Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Monday, March 16, 2015

.NET Cryptography: A Quick And Simplified View

This is just a quick and simplified view of the .NET cryptography generally used.

Of course, this is really a broad and deep subject, it just depends on how deep are you willing to dive into. ;)


Algorithms Preferences
1. Rijndael (strongest)
2. TripleDES (strong)
3. DES, RC2 (weak, please do not use if not necessary)


Rijndael Encryption/Decryption
- Algorithm : RijndaelManaged (C#)
- Mode : CBC
- Padding Mode : PKCS7
- Key : 32 ASCII chars (32 bytes / 256 bits)
- IV : 16 ASCII chars (16 bytes / 128 bits)
- Same as AES-256

- To use RijndaelManaged as AES (or AES compliant):
- Mode : CBC
- Key Size : 128 / 192 / 256 bits (default 256 bits, means AES-256)
- Block Size : 128 bits (default)

- The Differences Between Rijndael and AES.


TripleDES Encryption/Decryption
- Algorithm : TripleDESCryptoServiceProvider (C#)
- Mode : CBC
- Padding Mode : PKCS7
- Key : 24 ASCII chars (24 bytes / 192 bits)
- IV : 8 ASCII chars ( 8 bytes / 64 bits)



If you find this post helpful, would you buy me a coffee?


Sunday, September 15, 2013

Object And XML Serialization

In .Net, we can serialize and deserialize an object to/from an XML string using XmlSerializer.

The following methods provide easy ways to do this, with overloads that accept an existing instance of XmlSerializer if there is any.
  • Note: To avoid any performance hit, it's always better to use back an existing instance of XmlSerializer for the same object type if possible.

using System.IO;
using System.Xml;
using System.Xml.Serialization;

...

/// <summary>
/// Serializes an object to XML string.
/// </summary>
/// <param name="obj">Object to be serialized.</param>
/// <returns>Serialized XML string.</returns>
public static string SerializeObjectToXML(object obj)
{
    return SerializeObjectToXML(obj, null);
}

/// <summary>
/// Serializes an object to XML string.
/// </summary>
/// <param name="obj">Object to be serialized.</param>
/// <param name="serializer">An XmlSerializer instance to be used. If null, a new instance will be used.</param>
/// <returns>Serialized XML string.</returns>
public static string SerializeObjectToXML(object obj, XmlSerializer serializer)
{
    string xmlString;

    using (StringWriter stringWriter = new StringWriter())
    {
        if (serializer == null)
            serializer = new XmlSerializer(obj.GetType());

        serializer.Serialize(stringWriter, obj);
        stringWriter.Flush();
        xmlString = stringWriter.ToString();
    }

    return xmlString;
}

/// <summary>
/// Deserializes XML string to an object.
/// </summary>
/// <param name="xmlString">Serialized XML string.</param>
/// <param name="objType">Type of object to deserialize.</param>
/// <returns>Object deserialized from XML.</returns>
public static object DeserializeXMLToObject(string xmlString, Type objType)
{
    return DeserializeXMLToObject(xmlString, objType, null);
}

/// <summary>
/// Deserializes XML string to an object.
/// </summary>
/// <param name="xmlString">Serialized XML string.</param>
/// <param name="objType">Type of object to deserialize.</param>
/// <param name="serializer">An XmlSerializer instance to be used. If null, a new instance will be used.</param>
/// <returns>Object deserialized from XML.</returns>
public static object DeserializeXMLToObject(string xmlString, Type objType, XmlSerializer serializer)
{
    object obj = null;

    using (StringReader stringReader = new StringReader(xmlString))
    {
        if (serializer == null)
            serializer = new XmlSerializer(objType);

        obj = serializer.Deserialize(stringReader);
        stringReader.Close();
    }

    return obj;
}


If you find this post helpful, would you buy me a coffee?


Thursday, September 12, 2013

Regasm COM Interop Assembly With .Net Installer Project

COM Interop allows COM developers to access managed objects as easily as they access other COM objects. COM Interop provides a specialized utility (RegAsm.exe) that exports the managed types into a type library and registers the managed component as a traditional COM component.

To manually register a COM Interop assembly using RegAsm, we typically would do the following (assuming MyCom.COM.dll is an assembly that implements COM Interop for the managed MyCom.dll assembly):
  • Copy MyCom.COM.dll and MyCom.dll (and any other local assemblies) to a folder.
  • Run in VS command prompt: regasm [yourpath]\MyCom.COM.dll /tlb /codebase

However, it's also possible to include the Regasm process into a .Net Installer. Obviously this is the most ideal solution for easy distribution.

To implement this, I've largely benefited from Leon Mayne's blog Regasm /codebase for an installer project.

Below is a direct extract from the blog article about things to take note:

  • Making sure you do not use project references when adding your files to the installer. Use direct file references instead
  • Do the same for referenced files that are included automatically, as they do not get dropped in the obj folder and cause problems. Just exclude them from the project and then add them manually to the installer output
  • Make sure you have a type library for your dll and that this is included in the installer and set to register for COM
  • Make sure your dll is set to register for COM (duh)

Note: The first and second points may differ for some situations, as from my experience, I could use project references without any problem.

And another direct extract from the blog article that clearly stated the steps to perform:

  1. In your main project (the one containing the class you want to register), right click the project file and select Add / New Item and select Installer Class. Call it something like clsRegisterDll.cs
  2. In the designer that appears, click 'Click here to switch to code view' or right click the clsRegisterDll.cs file in solution explorer and select View Code
  3. Replace the code in the window with the code listed below, and make sure you change 'YourNamespace'. This has to match with the namespace in the clsRegisterDll.Designer.cs file.
  4. Compile your project
  5. In your installer, make sure you have added your dll to the Application Folder, and then right-click the installer project and select View / Custom Actions
  6. Right-click Install, and then click Add Custom Action
  7. Double click on Application Folder, and then on your dll
  8. Do the same for the Commit action
  9. Build and test your installer

Ok, now for the clsRegisterDll.cs class. In my implementation, I've renamed this class to RegasmTlbInstaller.cs, made some code changes, and also added overrides for Rollback and Uninstall.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Collections;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;

namespace YourNamespace
{
    /// <summary>
    /// An installer class to run regasm /tlb /codebase on the COM DLL (register and un-register).
    /// </summary>
    [RunInstaller(true)]
    public partial class RegasmTlbInstaller : Installer
    {
        public RegasmTlbInstaller()
        {
            InitializeComponent();
        }

        [SecurityPermission(SecurityAction.Demand)]
        public override void Install(IDictionary savedState)
        {
            base.Install(savedState);
        }

        /// <summary>
        /// Runs regasm /tlb /codebase to create and register the TLB file.
        /// </summary>
        [SecurityPermission(SecurityAction.Demand)]
        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);

            // Get the location of regasm.
            string regasmPath       = RuntimeEnvironment.GetRuntimeDirectory() + @"regasm.exe";

            // Get the location of our DLL.
            string componentPath    = typeof(RegasmTlbInstaller).Assembly.Location;

            // Execute regasm.
            Process p               = new Process();
            p.StartInfo.FileName    = regasmPath;
            p.StartInfo.Arguments   = "\"" + componentPath + "\" /tlb /codebase";
            p.StartInfo.Verb        = "runas";          // To run as administrator.
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.Start();
            p.WaitForExit();
        }

        [SecurityPermission(SecurityAction.Demand)]
        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);
        }

        /// <summary>
        /// Runs regasm /u /tlb /codebase to un-register and delete the TLB file.
        /// </summary>
        [SecurityPermission(SecurityAction.Demand)]
        public override void Uninstall(IDictionary savedState)
        {
            // Get the location of regasm.
            string regasmPath       = RuntimeEnvironment.GetRuntimeDirectory() + @"regasm.exe";

            // Get the location of our DLL.
            string componentPath    = typeof(RegasmTlbInstaller).Assembly.Location;

            // Execute regasm.
            Process p               = new Process();

            p.StartInfo.FileName    = regasmPath;
            p.StartInfo.Arguments   = "/u \"" + componentPath + "\" /tlb /codebase";
            p.StartInfo.Verb        = "runas";          // To run as administrator.
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.Start();
            p.WaitForExit();

            // Delete the TLB file.
            File.Delete(Path.Combine(Path.GetDirectoryName(componentPath), Path.GetFileNameWithoutExtension(componentPath) + ".tlb"));

            base.Uninstall(savedState);
        }
    }
}

Since this class also implements Rollback and Uninstall overloads, we need to add Custom Action for Rollback and Uninstall in the installer project as well (as in steps 6 and 7 mentioned above).

Additionally, the installer project properties should typically set as follows:
  • Project properties:
    • DetectNewerInstallation: True
    • InstallAllUsers: True
    • RemovePreviousVersions: True
  • Primary Output properties:
    • Register: vsdrpCOM
    • SharedLegacy: True
    • (User's Desktop and User's Programs Menu leave empty)

Finally, after building the installer project, please make sure the TLB file generated (e.g. MyCom.COM.tlb) is excluded from the "Detected Dependencies" folder.


Additional Resources:
C# COM Interop And Events


If you find this post helpful, would you buy me a coffee?


Thursday, September 5, 2013

C# COM Interop And Events

By using COM Interop, it is possible to expose the events from your managed component to COM environment. I've used the sample in Exposing Com Events for making my own implementation as below.

This example shows how to create a project named MyCom.COM to allow COM Interop for MyCom.dll with Events:
  • MyCom is the original .Net managed assembly which consist of:
    • a public property SerialNo;
    • a method ReadData();
    • two events Progress and Error.
  • MyComHelper is a wrapper class for the original MyCom functions and events.
  • Interface classes IMyComHelper and IMyComHelperEvents are required by MyComHelper.

Steps to create:

1. In your project, add reference to System.EnterpriseServices.dll.

2. Add a class named as MyComHelper. Implementations of MyComHelper class and interfaces:
using System.Runtime.InteropServices;
using System.EnterpriseServices;

namespace MyCom.COM
{
    #region Interfaces

    #region IMyComHelper

    [ComVisible(true)]                                          // Exposed.
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]      // Our managed interface will be IDispatch.
    public interface IMyComHelper
    {
        string SerialNo { get; set; }
        bool ReadData();
    }

    #endregion

    #region IMyComHelperEvents

    [ComVisible(true)]                                          // Exposed.
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]      // Our managed interface will be IDispatch.
    public interface IMyComHelperEvents
    {
        void Progress(string data, decimal percent);
        void Error(string description, string source);
    }

    #endregion

    #endregion

    /// <summary>
    /// A COM wrapper class for the MyCom functions and events.
    /// </summary>
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(IMyComHelperEvents))]                       // Our event source is IMyComHelperEvents interface.
    [ProgId("MyCom_COM.MyComHelper")]
    public class MyComHelper : ServicedComponent, IMyComHelper
    {
        #region Declare

        MyCom _myCom = new MyCom();                                         // The MyCom class from MyCom.dll.

        #region Declare Events

        [ComVisible(false)]
        public delegate void ProgressDel(string data, decimal percent);     // No need to expose this delegate.
        public event ProgressDel Progress;

        [ComVisible(false)]
        public delegate void ErrorDel(string description, string source);
        public event ErrorDel Error;

        #endregion

        #region Declare Properties

        public string SerialNo
        {
            get { return _myCom.SerialNo; }
            set { _myCom.SerialNo = value; }
        }

        #endregion

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the MyComHelper object.
        /// </summary>
        public MyComHelper()
        {
            _myCom.Progress += new MyCom.ProgressEventHandler(_myCom_Progress);
            _myCom.Error    += new MyCom.ErrorEventHandler(_myCom_Error);
        }

        #endregion

        #region Methods

        public bool ReadData()
        {
            return _myCom.ReadData();   // In this example, data is returned in the Progress event.
        }

        #endregion

        #region Events

        void _myCom_Progress(string data, decimal percent)
        {
            Progress(data, percent);
        }

        void _myCom_Error(object sender, MyCom.ErrorEventArgs e)
        {
            Error(e.Description, e.Source);
        }

        #endregion
    }
}
3. In the project Properties > Application > Assembly Information > Make assembly COM-Visible > Check. Build the project.

4. Register the assembly using RegAsm:
  • Copy MyCom.COM.dll and MyCom.dll (and any other local assemblies) to a folder.
  • Run in VS command prompt: regasm [yourpath]\MyCom.COM.dll /tlb /codebase

5. Now you can add reference to MyCom.COM.dll from your unmanaged code, and will be able to access it's events.


Additional Resources:
Regasm COM Interop Assembly With .Net Installer Project


If you find this post helpful, would you buy me a coffee?


Sunday, September 1, 2013

Creating Events in C#

In C#, creating and consuming events are easy and definitely not a rocket science. However, from time to time, I find myself keep having trouble remembering how to implement it.

So, this is more a "note-to-self" post on creating... events.


Method 1:

public class MyClass
{
    /// <summary>
    /// MyClass event for getting data progress.
    /// </summary>
    public event GetDataProgressEventHandler GetDataProgress;

    /// <summary>
    /// MyClass event handler for getting data progress.
    /// </summary>
    /// <param name="percentage">Progress percentage.</param>
    /// <param name="message">Progress message.</param>
    public delegate void GetDataProgressEventHandler(decimal percentage, string message);

    ...
    
    public void GetData()
    {
        if (GetDataProgress!= null)
            GetDataProgress(((decimal)count / (decimal)max) * 100, statusMessage);    // Trigger the get data progress event.
    }
}

Usage Example:

MyClass myClass          = new MyClass();
myClass.GetDataProgress += new MyClass.GetDataProgressEventHandler(myClass_GetDataProgress);

...

void myClass_GetDataProgress(decimal progress, string message)
{
    ...
}


Method 2 (using Event Data):

public class MyClass
{
    /// <summary>
    /// Occurs when MyClass getting data giving progress.
    /// </summary>
    public event EventHandler<GetDataProgressEventArgs> GetDataProgress;

    /// <summary>
    /// Raises the GetDataProgress event of MyClass.
    /// </summary>
    /// <param name="e">The event argument.</param>
    protected virtual void OnGetDataProgress(GetDataProgressEventArgs e)
    {
        EventHandler<GetDataProgressEventArgs> handler = GetDataProgress;

        if (handler != null)
        {
            handler(this, e);
        }
    }

    ...
    
    public void GetData()
    {
        ...

        GetDataProgressEventArgs getDataProgressEventArgs = new GetDataProgressEventArgs();
        getDataProgressEventArgs.Percentage = ((decimal)count / (decimal)max) * 100;
        getDataProgressEventArgs.Message    = statusMessage;

        OnGetDataProgress(getDataProgressEventArgs);    // Trigger the get data progress event.
    }
}

For the event data, you need to define a class for GetDataProgressEventArgs:

/// <summary>
/// A derived EventArgs class used by MyClass to pass the GetDataProgress event data.
/// </summary>
public class GetDataProgressEventArgs : EventArgs
{
    private decimal _percentage = 0;

    /// <summary>
    /// Progress percentage.
    /// </summary>
    public decimal Percentage 
    { 
        get { return _percentage;  }
        set { _percentage = value; } 
    }

    private string _message = null;

    /// <summary>
    /// Progress status message.
    /// </summary>
    public string Message
    {
        get { return _message;  }
        set { _message = value; }
    }
}

Usage Example:

MyClass myClass          = new MyClass();
myClass.GetDataProgress += new EventHandler<GetDataProgressEventArgs>(myClass_GetDataProgress);

...

void myClass_GetDataProgress(object sender, GetDataProgressEventArgs e)
{
    ...
}


Additional Resources:
Handling and Raising Events



If you find this post helpful, would you buy me a coffee?


Thursday, August 29, 2013

Regex Tool For Testing Regex Patterns

This is a simple utility to match a Regex pattern which returns a Match (Match Single) or a MatchCollection (Match All), optionally with the RegexOptions specified.

The matched group(s) are displayed in the "Result" box at the bottom.

Please note that the first matched group (Group 0) of each Match is always the entire matched value.


Source Code:
The project source code is available as a public Mercurial repository hosted on BitBucket. You are free to download/clone/fork this project using the URL below:
https://bitbucket.org/simpcode/weizh.regextool

Executable:
Alternatively, you can download the executable from this link.


Screenshot:




If you find this post helpful, would you buy me a coffee?


Wednesday, August 28, 2013

Get Key Value From String

There are times when we need to store a key-value pair string (in a variable, fields, etc.), and then later read back the values from there.

For example, we may store a key-value pair in this format:
[key=value]
For multiple key-value pairs, we could store them in this way:
[key1=value1][key2=value2][key3=value3]

So, to read back the key-value pair string values, we can use the following methods:
/// <summary>
/// Gets value from a key-value pair string. In format [key=value].
/// </summary>
/// <param name="source">The source string to check.</param>
/// <param name="key">The key to find.</param>
/// <returns>The key's value.</returns>
public static string GetKeyValueFromString(string source, string key)
{
    return GetKeyValueFromString(source, key, "[", "]", "=");
}

/// <summary>
/// Gets value from a key-value pair string, in format with the signs provided. E.g. [key=value].
/// </summary>
/// <param name="source">The source string to check.</param>
/// <param name="key">The key to find.</param>
/// <param name="encloseLeftSign">Left enclosure sign.</param>
/// <param name="encloseRightSign">Right enclosure sign.</param>
/// <param name="equalSign">Equal sign.</param>
/// <returns>The key's value.</returns>
public static string GetKeyValueFromString(string source, string key, string encloseLeftSign, string encloseRightSign, string equalSign)
{
    if (String.IsNullOrEmpty(encloseLeftSign) || String.IsNullOrEmpty(encloseRightSign) || String.IsNullOrEmpty(equalSign))
        throw new ArgumentException("encloseLeftSign, encloseRightSign, equalSign arguments cannot be null or empty.");     // Validate signs.

    if (String.IsNullOrEmpty(source) || String.IsNullOrEmpty(key))
        return "";

    key = encloseLeftSign + key + equalSign;

    if (source.IndexOf(key) < 0)
        return "";

    string value = "";

    value = source.Substring(source.IndexOf(key) + key.Length);
    value = value.Substring(0, value.IndexOf(encloseRightSign));

    return value;
}

Note:
The first overload reads the value by using the default enclosure and equal signs (as in [key=value]), whereas the second overload reads the value by using the specified enclosures and equal signs (e.g. {key:value}, <key#value>, etc.).


Example:

// Store 3 key-value pairs into a "data" variable.
string data = "[code=3305][name=Peter Ross][url=www.example.com]";

// Read back the key values from the "data" variable.
string code = GetKeyValueFromString(data, "code");    // Value is "3305".
string name = GetKeyValueFromString(data, "name");    // Value is "Peter Ross".
string url  = GetKeyValueFromString(data, "url");     // Value is "www.example.com".



If you find this post helpful, would you buy me a coffee?


Monday, August 26, 2013

Properly Deleting A Directory, Optionally Using Regex Patterns

This method performs a deletion of a directory, and it's subdirectories and files.

Additionally, it will try to set all files to non-readonly before they're being deleted.

Optionally, it also allows to specify Regex patterns to match the names of the subdirectories or files to be deleted or excluded from the deletion (Regex options used are IgnoreCase and Singleline).

/// <summary>
/// Deletes the specified directory, subdirectories and files. 
/// <para>All files will be set to non-readonly before deleting.</para>
/// <para>_</para>
/// <para>Optionally, indicate the Regex patterns to match the names of the subdirectories or files to be deleted or excluded from deletion.</para>
/// <para>Note: Regex options used are IgnoreCase and Singleline.</para>
/// </summary>
/// <param name="path">The name of the directory to remove.</param>
/// <param name="subdirRegexPattern">Regex pattern for the name of the subdirectories to be deleted. Set null or empty for no checking.</param>
/// <param name="subdirExcludeRegexPattern">Regex pattern for the name of the subdirectories to be excluded from deletion. Set null or empty for no checking.</param>
/// <param name="fileRegexPattern">Regex pattern for the name of the files to be deleted. Set null or empty for no checking.</param>
/// <param name="fileExcludeRegexPattern">Regex pattern for the name of the files to be excluded from deletion. Set null or empty for no checking.</param>
public static void DeleteDirectory(string path, 
    string subdirRegexPattern, string subdirExcludeRegexPattern,
    string fileRegexPattern, string fileExcludeRegexPattern)
{
    if (!Directory.Exists(path))
        throw new DirectoryNotFoundException();

    DirectoryInfo dirInfo = new DirectoryInfo(path);

    #region Delete the files

    foreach (FileInfo fileInfo in dirInfo.GetFiles())
    {
        bool doDelete = true;

        #region Check regex patterns if any

        // Note: The static Regex.IsMatch() will take advantage of regex cache (default to last 15).

        if (!String.IsNullOrEmpty(fileRegexPattern) &&
            !Regex.IsMatch(fileInfo.Name, fileRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline))
            doDelete = false;

        if (!String.IsNullOrEmpty(fileExcludeRegexPattern) &&
            Regex.IsMatch(fileInfo.Name, fileExcludeRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline))
            doDelete = false;                   // Note: Exclude will take precedence.

        #endregion

        if (doDelete)
        {
            fileInfo.IsReadOnly = false;        // Make sure is not ReadOnly before delete.
            fileInfo.Delete();
        }
    }

    #endregion

    #region Delete the subdirectories

    foreach (DirectoryInfo subDirInfo in dirInfo.GetDirectories())
    {
        bool doDelete = true;

        #region Check regex patterns if any

        // Note: The static Regex.IsMatch() will take advantage of regex cache (default to last 15).

        if (!String.IsNullOrEmpty(subdirRegexPattern) &&
            !Regex.IsMatch(subDirInfo.Name, subdirRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline))
            doDelete = false;

        if (!String.IsNullOrEmpty(subdirExcludeRegexPattern) &&
            Regex.IsMatch(subDirInfo.Name, subdirExcludeRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline))
            doDelete = false;                   // Note: Exclude will take precedence.

        #endregion

        if (doDelete)
        {
            DeleteDirectory(subDirInfo.FullName, 
                subdirRegexPattern, subdirExcludeRegexPattern,
                fileRegexPattern, fileExcludeRegexPattern);     // Recursive.
        }
    }

    #endregion

    #region Delete the directory itself

    if (dirInfo.GetFiles().Length <= 0 && dirInfo.GetDirectories().Length <= 0)
    {
        dirInfo.Delete();                       // Delete only if no content.

        #region Make sure the directory is deleted before continue

        #region Note
        /*
         * To solve the intermittent file/directory deletion lagging issue, use retry if directory still exist.
         * Reference: http://stackoverflow.com/questions/9370012/waiting-for-system-to-delete-file
        */
        #endregion

        int retry = 0;
        while (dirInfo.Exists && retry <= 4)
        {
            retry += 1;
            System.Threading.Thread.Sleep(500); // Delay for half-second.

            // Note: If done all retries and still exist, do nothing and let the caller to handle (any delete exception will still be thrown).
        }

        #endregion
    }

    #endregion
}

Usage Examples:

To completely delete a directory and all it's contents (without Regex):
DeleteDirectory(@"D:\MyData\MyFolder", null, null, null, null);

To completely delete a directory and all it's contents, excluding subdirectories named "KeepDir" and files named "KeepFile.txt" (based on the Regex patterns specified):
DeleteDirectory(@"D:\MyData\MyFolder", 
    null, @"\bKeepDir\b",
    null, @"\bKeepFile.txt\b");



If you find this post helpful, would you buy me a coffee?


Sunday, August 25, 2013

Resolve Assembly By OS Bitness

The AppDomain.CurrentDomain.AssemblyResolve event fires when the CLR tries to load an assembly and fails. This also could happen if the assembly is "OS bitness" dependent and we tried to load a 32-bit assembly in a 64-bit OS and vice-versa.

Instead of maintaining separate setups for different OS system bit, we could solve this by calling the "ResolveAssemblyByOSBitness" method inside the AssemblyResolve event. With this method, both the 32-bit and 64-bit assemblies (must be in same version) are embedded into our project. At runtime, after checking the OS system bit, it'll copy the correct embedded assembly to the system's temporary folder named with it's file name and version (if not already exist). Then it uses "Assembly.LoadFrom" to load the assembly.

Note:
  • This method can load a fully managed assembly or a mixed assembly with unmanaged code.
  • Assembly.LoadFrom would also solve the "Unverifiable code failed policy check" error when loading a mixed assembly.


1. Embed the assembly files:
- In the project, the embedded assembly file must be:
  • put in folder embed\x86 or embed\x64 respectively, and
  • set Build Action to 'Embedded Resource'.
Note: You still need to add reference to either the 32 or 64-bit assembly as normal (base on your development OS) so that the project can be compiled.

2. Create a class named "MyUtil" (or any name you preferred).

3. Copy the "CSCheckOSBitness" code from here and put into the MyUtil class.

Note: Please modify the code to fit into the MyUtil class instead of Program class. Please comment out the Main() method as well as it's not needed here.

4. Add the following method into the MyUtil class (reference):
/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ((len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }
}

5. Add the following method into the MyUtil class:
using System.IO;
using System.Reflection;
using System.Diagnostics;

...

/// <summary>
/// Resolves mixed assembly with unmanaged code for 32-bit and 64-bit OS.
/// <para>
/// To be called from the AppDomain.CurrentDomain.AssemblyResolve event.
/// </para>
/// </summary>
/// <param name="sourceAssembly">
/// The assembly that contains the embedded assembly file to resolve.
/// <para>The embedded assembly file must be:</para>
/// <para>- put in folder embed\x86 or embed\x64, and</para>
/// <para>- set Build Action to 'Embedded Resource'.</para>
/// </param>
/// <param name="argsName"><see cref="ResolveEventArgs"/>'s item name.</param>
/// <param name="assemblyName">Assembly name to resolve (case sensitive and includes extension).</param>
/// <param name="assemblyFileVersion">Assembly file version to resolve.</param>
/// <returns>Resolved assembly.</returns>
public static Assembly ResolveAssemblyByOSBitness(Assembly sourceAssembly, string argsName, string assemblyName, string assemblyFileVersion)
{
    // Trim any empty spaces.
    argsName            = argsName.Trim();
    assemblyName        = assemblyName.Trim();
    assemblyFileVersion = assemblyFileVersion.Trim();
    // ==

    // The assembly names must be matched with exact case, else return null.
    if (!Path.GetFileNameWithoutExtension(assemblyName).Equals(new AssemblyName(argsName).Name, StringComparison.Ordinal))
        return null;
    //==

    string osBitness          = Util.Is64BitOperatingSystem() ? "x64" : "x86";

    #region Check entry assembly target platform

    Assembly entryAssembly = Assembly.GetEntryAssembly();
    if (entryAssembly != null)
    {
        if (entryAssembly.GetName().ProcessorArchitecture == ProcessorArchitecture.X86)
            osBitness = "x86";          // Always set to "x86" if target platform is "x86".
    }

    #endregion

    string sourceAssemblyName = sourceAssembly.GetName().Name.Trim();

    string assemblyPath       = Path.Combine(Path.Combine(Path.GetTempPath(), sourceAssemblyName),
        Path.GetFileNameWithoutExtension(assemblyName) + "_" + osBitness + "_" + assemblyFileVersion);

    string assemblyFile       = Path.Combine(assemblyPath, assemblyName);

    if (!File.Exists(assemblyFile))
    {
        if (!Directory.Exists(assemblyPath))
            Directory.CreateDirectory(assemblyPath);

        // Copy the embedded assembly file to the system's temporary folder.
        string resourceName = sourceAssemblyName + ".embed." + osBitness + "." + assemblyName;

        using (Stream stream = sourceAssembly.GetManifestResourceStream(resourceName))
        {
            using (Stream file = File.OpenWrite(assemblyFile))
            {
                CopyStream(stream, file);
            }
        }
        // ==

        // Delete the folder if the assembly's file version not matched, and return null.
        if (!FileVersionInfo.GetVersionInfo(assemblyFile).FileVersion.Equals(assemblyFileVersion))
        {
            Directory.Delete(assemblyPath, true);
            return null;
        }
        // ==
    }

    return Assembly.LoadFrom(assemblyFile);    // Load the assembly from the systems's temporary folder.
}

6. When the application initializes (or at your entry class constructor), register the AppDomain’s AssemblyResolve event:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Add the event handler:
internal static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    return MyUtil.ResolveAssemblyByOSBitness(
        Assembly.GetExecutingAssembly(), args.Name, "SampleAssembly.Sample.dll", "1.0.0.0");
}


Example:
This is an example that I used to load a 64-bit SQLite assembly (version 1.0.76) in a 64-bit OS. In the AssemblyResolve event handler, I would use:
return MyUtil.ResolveAssemblyByOSBitness(
    Assembly.GetExecutingAssembly(), args.Name, "System.Data.SQLite.DLL", "1.0.76");
Once this method has been called, the System.Data.SQLite.DLL will be loaded from the System's Temp folder in ...\Temp\MyAssembly\System.Data.SQLite_x64_1.0.76.0.

Note: MyAssembly is the assembly name where the MyUtil class is resided.


Additional Resources:
How to load an assembly at runtime that is located in a folder that is not the bin folder of the application (Method 3: Use the AssemblyResolve event)


If you find this post helpful, would you buy me a coffee?


Tuesday, August 14, 2012

Assemblies Embedding And Obfuscation With Eazfuscator.NET

We could use ILMerge to merge our .Net assemblies. However, instead of merging, we could also "embed" our assemblies (reference). In fact, there are many obfuscation tools in the market which not only obfuscate, but also help to merge/embed assemblies in a single build process.

Eazfuscator.NET is one of them which is easy to use and is free. If what you need is some level of protection to prevent reverse engineering of your code, as well as assembly merging and embedding (it supports both), this is a handy tool to use.

Update: Eazfuscator.NET went commercial and is no longer free starting from version 3.4.71 (released on 2012-06-22).

The following steps show you how to embed and obfuscate your assemblies using Eazfuscator.NET:
  • Disclaimer: This is only a guideline based on my commonly used scenario, you may want to refer the Eazfuscator.NET Documentation for more options and functionality.

1. Download and install Eazfuscator.NET (Close all opening Visual Studio before install).

2. Open your project in Visual Studio. You will see "Eazfuscator.NET Assistant" under the Visual Studio's main menu > "Tools". You can also access this from the Windows "Start" menu.

3. Drag and drop your main (StartUp) project from the Solution Explorer to the "Green" box. Now this project is set for obfuscation.
  • Note: Drag and drop the project to the "Red" box to disable the obfuscation.

4. Add a new CS file named as "ObfuscationSettings.cs". Move this file into the "Properties" folder. Put the following contents into the file:
using System;
using System.Reflection;

[assembly: Obfuscation(Feature = "encrypt symbol names with password MyP@ssw0rd", Exclude = false)]
[assembly: Obfuscation(Feature = "code control flow obfuscation", Exclude = false)]
If Reflection is used within your project, you may add this line:
[assembly: Obfuscation(Feature = "Apply to * when public and class: renaming", Exclude = true)]
Note:
  • Replace MyP@ssw0rd with your password.
  • If all your projects use the same settings, you may put these into a commonly shared link file (e.g. CommonObfuscationSettings.cs) and add as link from your project.

5. In "ObfuscationSettings.cs", add attributes for each referenced .Net assembly you want to embed, for example:
[assembly: Obfuscation(Feature = "embed My.SupportAssembly1.dll", Exclude = false)]
[assembly: Obfuscation(Feature = "embed My.SupportAssembly2.dll", Exclude = false)]
[assembly: Obfuscation(Feature = "embed MySql.Data.dll", Exclude = false)]
Note:
  • The assembly file name is case-sensitive.
  • Mixed assembly (assembly which contains unmanaged code) is well supported.

6. Build the project. The main project's bin\Release will have the assembly which is obfuscated and can be deployed without the referenced assemblies.


Additional Resources:
Assemblies Merging With Post-build Event
Assemblies Merging With ILMerge and MSBuild


If you find this post helpful, would you buy me a coffee?