November 2014

Sun Mon Tue Wed Thu Fri Sat
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            










« Building an Installer – Part 3 | Main | Getting to know my Eye-Fi card in preparation for AU 2010 »

September 21, 2010

An updated version of RegDL available

As mentioned in the last post, I decided to update the RegDL tool – which can be used to create demand-loading entries for an AutoCAD .NET module from, for instance, an installer – to support optional logging to a file. If you now run RegDL.exe with the /log command-line switch, then the application will now create a text file, reglog.txt, in the executable’s folder with the high-level results of the registration (with hopefully some useful detail in case of failure).

Here are the main, updated files, Program.cs:

using System.Reflection;

using System.IO;

using System;

using DemandLoading;

 

namespace RegDL

{

  class Program

  {

    static void Main(string[] args)

    {

      // We need at least one argument (the assembly name)

      // or the request for help

 

      if (args.Length <= 0 ||

          args.Length == 1 &&

          (args[0] == "/?" || args[0].ToLower() == "/help"))

      {

        PrintUsage();

        return;

      }

 

      // Get the first argument and check the file exists

 

      string asmName = args[0];

      if (!File.Exists(asmName))

      {

        Log(

          String.Format(

            "RegDL : Unable to locate input assembly '{0}'.",

            asmName

          ),

          false

        );

        return;

      }

 

      // Now we get the optional flags

 

      bool startup = false;

      bool hklm = false;

      bool unreg = false;

      bool force = false;

      bool log = false;

 

      for (int i=1; i < args.Length; i++)

      {

        string arg = args[i].ToLower();

        startup |= (arg == "/startup");

        hklm |= (arg == "/hklm");

        unreg |= (arg == "/unregister");

        force |= (arg == "/force");

        log |= (arg == "/log");

      }

 

      // As each dependent assembly is resolved, we need to make

      // sure it is loaded via Reflection-Only, not a full Load

 

      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=

        delegate(object sender, ResolveEventArgs rea)

        {

          return Assembly.ReflectionOnlyLoad(rea.Name);

        };

 

      // Let's load our assembly via Reflection-Only

 

      Assembly assem =

        Assembly.ReflectionOnlyLoadFrom(asmName);

 

      bool res = false;

 

      if (unreg)

      {

        // Unregister the assembly, if possible

 

        try

        {

          res = RegistryUpdate.UnregisterForDemandLoading(assem);

        }

        catch(Exception ex)

        {

          Log(String.Format("Exception: {0}", ex), log);

        }

 

        if (res)

        {

          Log(

            String.Format(

              "Removed demand-loading information for '{0}'.",

              asmName

            ),

            log

          );

        }

        else

        {

          Log(

            String.Format(

            "Could not remove demand-loading information for '{0}'.",

              asmName

            ),

            log

          );

        }

      }

      else

      {

        // Register the assembly, if possible

 

        try

        {

          res =

            RegistryUpdate.RegisterForDemandLoading(

              assem, !hklm, startup, force

          );

        }

        catch(Exception ex)

        {

          if (ex is ReflectionTypeLoadException)

          {

            Log(

              "Trouble loading types: do you have AcMgd.dll and " +

              "AcDbMgd.dll in the same folder as RegDL.exe?", log

            );

          }

          else

          {

            Console.WriteLine("Exception: {0}", ex);

          }

        }

 

        if (res)

        {

          Log(

            String.Format(

            "Registered assembly '{0}' for AutoCAD demand-loading.",

              asmName

            ),

            log

          );

        }

        else

        {

          Log(

            String.Format(

              "Could not register '{0}' for AutoCAD demand-loading.",

              asmName

            ),

            log

          );

        }

      }

    }

 

    // Print a usage message

 

    static void PrintUsage()

    {

      const string indent = "    ";

      const string start = "Version=";

 

      string version =

        Assembly.GetExecutingAssembly().FullName;

 

      if (version.Contains(start))

      {

        // Get the string starting with the version number

 

        version =

          version.Substring(

            version.IndexOf(start) + start.Length

          );

 

        // Strip off anything after (and including) the comma

 

        version =

          version.Remove(version.IndexOf(','));

      }

      else

        version = "";

 

      Console.WriteLine(

        "AutoCAD .NET Assembly Demand-Loading Registration " +

        "Utility {0}", version

      );

      Console.WriteLine(

        "Written by Kean Walmsley, Autodesk."

      );

      Console.WriteLine(

        "http://blogs.autodesk.com/through-the-interface"

      );

      Console.WriteLine();

      Console.WriteLine("Syntax: RegDL AssemblyName [Options]");

      Console.WriteLine("Options:");

      Console.WriteLine(

        indent +

        "/unregister  Remove demand-loading keys for this assembly"

      );

      Console.WriteLine(

        indent +

        "/hklm        Write keys under HKLM rather than HKCU"

      );

      Console.WriteLine(

        indent +

        "/startup    Assembly to be loaded on AutoCAD startup"

      );

      Console.WriteLine(

        indent +

        "/force      Overwrite keys, should they already exist"

      );

      Console.WriteLine(

        indent +

        "/log        All output goes to a log file"

      );

      Console.WriteLine(

        indent +

        "/? or /help  Display this usage message"

      );

    }

 

    static internal void Log(string str, bool toFile)

    {

      if (toFile)

      {

        Assembly asm = Assembly.GetExecutingAssembly();

        string path = Path.GetDirectoryName(asm.Location);

        string logfile = path + "\\reglog.txt";

 

        // Create a writer and open the file

 

        StreamWriter log;

 

        if (!File.Exists(logfile))

        {

          log = new StreamWriter(logfile);

        }

        else

        {

          log = File.AppendText(logfile);

        }

 

        // Write to the file:

 

        log.WriteLine(DateTime.Now);

        log.WriteLine(str);

        log.WriteLine();

 

        // Close the stream:

        log.Close();

      }

      else

      {

        Console.WriteLine(str);

      }

    }

  }

}

And demand-loading-external.cs:

using System.Collections.Generic;

using System.Reflection;

using System.Resources;

using System;

using Microsoft.Win32;

 

namespace DemandLoading

{

  public class RegistryUpdate

  {

    public static bool RegisterForDemandLoading(

      Assembly assem, bool currentUser, bool startup, bool force

    )

    {

      // Get the assembly, its name and location

 

      string name = assem.GetName().Name;

      string path = assem.Location;

 

      // We'll collect information on the commands

      // (we could have used a map or a more complex

      // container for the global and localized names

      // - the assumption is we will have an equal

      // number of each with possibly fewer groups)

 

      List<string> globCmds = new List<string>();

      List<string> locCmds = new List<string>();

      List<string> groups = new List<string>();

 

      // Iterate through the modules in the assembly

 

      Module[] mods = assem.GetModules(true);

      foreach (Module mod in mods)

      {

        // Within each module, iterate through the types

 

        Type[] types = mod.GetTypes();

 

        foreach (Type type in types)

        {

          // We may need to get a type's resources

 

          ResourceManager rm =

            new ResourceManager(type.FullName, assem);

          rm.IgnoreCase = true;

 

          // Get each method on a type

 

          MethodInfo[] meths = type.GetMethods();

          foreach (MethodInfo meth in meths)

          {

            // Get the method's custom attribute(s)

 

            IList<CustomAttributeData> attbs =

              CustomAttributeData.GetCustomAttributes(meth);

 

            foreach (CustomAttributeData attb in attbs)

            {

              // We only care about our specific attribute type

 

              if (attb.Constructor.DeclaringType.Name ==

                  "CommandMethodAttribute")

              {

                // Harvest the information about each command

 

                string grpName = "";;

                string globName = "";

                string locName = "";

                string lid = "";

 

                // Our processing will depend on the number of

                // parameters passed into the constructor

 

                int paramCount = attb.ConstructorArguments.Count;

 

                if (paramCount == 1 || paramCount == 2)

                {

                  // Constructor options here are:

 

                  //  globName (1 argument)

                  //  grpName, globName (2 args)

 

                  globName =

                    attb.ConstructorArguments[0].ToString();

                  locName = globName;

                }

                else if (paramCount >= 3)

                {

                  // Constructor options here are:

 

                  //  grpName, globName, flags (3 args)

                  //  grpName, globName, locNameId, flags (4 args)

                  //  grpName, globName, locNameId, flags,

                  //    hlpTopic (5 args)

                  //  grpName, globName, locNameId, flags,

                  //    contextMenuType (5 args)

                  //  grpName, globName, locNameId, flags,

                  //    contextMenuType, hlpFile, helpTpic (7 args)

 

                  CustomAttributeTypedArgument arg0, arg1;

                  arg0 = attb.ConstructorArguments[0];

                  arg1 = attb.ConstructorArguments[1];

 

                  // All options start with grpName, globName

 

                  grpName = arg0.Value as string;

                  globName = arg1.Value as string;

                  locName = globName;

 

                  // If we have a localized command ID,

                  // let's look it up in our resources

 

                  if (paramCount >= 4)

                  {

                    // Get the localized string ID

 

                    lid = attb.ConstructorArguments[2].ToString();

 

                    // Strip off the enclosing quotation marks

 

                    if (lid != null && lid.Length > 2)

                      lid = lid.Substring(1, lid.Length - 2);

 

                    // Let's put a try-catch block around this

                    // Failure just means we use the global

                    // name twice (the default)

 

                    if (lid != null && lid != "")

                    {

                      try

                      {

                        locName = rm.GetString(lid);

                      }

                      catch

                      { }

                    }

                  }

                }

 

                if (globName != null)

                {

                  // Add the information to our data structures

 

                  globCmds.Add(globName);

                  locCmds.Add(locName);

 

                  if (grpName != null && !groups.Contains(grpName))

                    groups.Add(grpName);

                }

              }

            }

          }

        }

      }

 

      // Let's register the application to load on AutoCAD

      // startup (2) if specified or if it contains no

      // commands. Otherwise we will have it load on

      // command invocation (12)

 

      int flags = (!startup && globCmds.Count > 0 ? 12 : 2);

 

      // Now create our Registry keys

 

      return CreateDemandLoadingEntries(

        name, path, globCmds, locCmds, groups,

        flags, currentUser, force

      );

    }

 

    public static bool UnregisterForDemandLoading(Assembly assem)

    {

      // Get the name of the application to unregister

 

      string appName = assem.GetName().Name;

 

      // Unregister it for both HKCU and HKLM

 

      bool res = RemoveDemandLoadingEntries(appName, true);

      res &= RemoveDemandLoadingEntries(appName, false);

 

      // If one call failed, we also fail (could change this)

 

      return res;

    }

 

    // Helper functions

 

    private static bool CreateDemandLoadingEntries(

      string appName,

      string path,

      List<string> globCmds,

      List<string> locCmds,

      List<string> groups,

      int flags,

      bool currentUser,

      bool force

    )

    {

      string ackName = GetAutoCADKey();

      RegistryKey hive =

        (currentUser ? Registry.CurrentUser : Registry.LocalMachine);

 

      // We may need to create the Applications key, as some AutoCAD

      // verticals do not contain it under HKCU by default

 

      // CreateSubKey just opens existing keys for write, anyway

 

      RegistryKey appk =

        hive.CreateSubKey(ackName + "\\" + "Applications");

      using (appk)

      {

        // Already registered? Just return (unless forcing)

 

        if (!force)

        {

          string[] subKeys = appk.GetSubKeyNames();

          foreach (string subKey in subKeys)

          {

            if (subKey.Equals(appName))

            {

              return false;

            }

          }

        }

 

        // Create the our application's root key and its values

 

        RegistryKey rk = appk.CreateSubKey(appName);

        using (rk)

        {

          rk.SetValue(

            "DESCRIPTION", appName, RegistryValueKind.String

          );

          rk.SetValue("LOADCTRLS", flags, RegistryValueKind.DWord);

          rk.SetValue("LOADER", path, RegistryValueKind.String);

          rk.SetValue("MANAGED", 1, RegistryValueKind.DWord);

 

          // Create a subkey if there are any commands...

 

          if ((globCmds.Count == locCmds.Count) &&

              globCmds.Count > 0)

          {

            RegistryKey ck = rk.CreateSubKey("Commands");

            using (ck)

            {

              for (int i = 0; i < globCmds.Count; i++)

                ck.SetValue(

                  globCmds[i],

                  locCmds[i],

                  RegistryValueKind.String

                );

            }

          }

 

          // And the command groups, if there are any

 

          if (groups.Count > 0)

          {

            RegistryKey gk = rk.CreateSubKey("Groups");

            using (gk)

            {

              foreach (string grpName in groups)

                gk.SetValue(

                  grpName, grpName, RegistryValueKind.String

                );

            }

          }

        }

      }

      return true;

    }

 

    private static bool RemoveDemandLoadingEntries(

      string appName, bool currentUser

    )

    {

      try

      {

        string ackName = GetAutoCADKey();

 

        // Choose a Registry hive based on the function input

 

        RegistryKey hive =

          (currentUser ?

            Registry.CurrentUser :

            Registry.LocalMachine);

 

        // Open the applications key

 

        RegistryKey appk =

          hive.OpenSubKey(ackName + "\\" + "Applications", true);

        using (appk)

        {

          // Delete the key with the same name as this assembly

 

          appk.DeleteSubKeyTree(appName);

        }

      }

      catch

      {

        return false;

      }

      return true;

    }

 

    private static string GetAutoCADKey()

    {

      // Start by getting the CurrentUser location

 

      RegistryKey hive = Registry.CurrentUser;

 

      // Open the main AutoCAD key

 

      RegistryKey ack =

        hive.OpenSubKey(

          "Software\\Autodesk\\AutoCAD"

        );

      using (ack)

      {

        // Get the current major version and its key

 

        string ver = ack.GetValue("CurVer") as string;

        if (ver == null)

        {

          throw new System.Exception(

            "Could not find major CurVer."

          );

        }

        else

        {

          RegistryKey verk = ack.OpenSubKey(ver);

          using (verk)

          {

            // Get the vertical/language version and its key

 

            string lng = verk.GetValue("CurVer") as string;

            if (lng == null)

            {

              throw new System.Exception(

                "Could not find local/vertical CurVer."

              );

            }

            else

            {

              RegistryKey lngk = verk.OpenSubKey(lng);

              using (lngk)

              {

                // And finally return the path to the key,

                // without the hive prefix

 

                return lngk.Name.Substring(hive.Name.Length + 1);

              }

            }

          }

        }

      }

    }

  }

}

I won’t go into greater detail regarding the specific changes, I’ll just leave you with the updated source project and executable.

Update

I've updated the source and downloadable project to fix the issue reported in this comment.

blog comments powered by Disqus

Feed/Share

10 Random Posts