0

I have .NET framework assembly which is exposed to COM. Recently, since the assembly minor version number reach 10, it stopped being usable from Windows Script Host (WSH).

[assembly: AssemblyVersion("1.10.0.0")]

The TypeLib registry entry for some obscure reason uses hexadecimal notation, when storing the version number:
BUG: The Regasm Tool Registers the Type Library with the Decimal Value Instead of the Hexadecimal Value

In this case it would be 1.a:

[HKEY_CLASSES_ROOT\TypeLib\{D766F0C8-9968-4A72-B918-D2780AEE7399}\1.a]
@="myassembly"

When the assembly is referenced in WSF file, WSH/cscript breaks whenever af "digits" are present in the version number. So it works for 1.0 or 1.9, does not work for 1.10 (1.a) or 1.15 (1.f), but works again for 1.16 (1.10) or 1.25 (1.19), and does not work again for 1.26 (1.1a) (I didn't check all intermediate values, but the pattern seems to be clear).

<job>
<reference object="myassembly.Class1" />
<script language="JScript">
...
</script>
</job>

The cscript says:

Windows Script Host: Cannot add reference : {D766F0C8-9968-4A72-B918-D2780AEE7399}

It seems like a bug in the WSH/cscript to me. Or am I missing something?

0

2 Answers 2

2

For the WSH reference node to work it must be capable of loading a COM Automation Type library (TLB) from the reference node.

There are two ways for that:

  1. the COM server embeds a TYPELIB Win32 resource
  2. the CLSID registry key associated with the COM server indicates what is the TLB Id and its version

It works w/o effort with COM servers such as ADODB.Recordset because they are in case 1).

In the .NET case, the .NET .dll doesn't embed any TLB information so you must indicate it. So to fix it you must:

  • Create a HKEY_CLASSES_ROOT\CLSID\{yourclsid}\TypeLib with a default value equal to your TYPELIB identifier formmated as {guid}
  • Create a HKEY_CLASSES_ROOT\CLSID\{yourclsid}\Version with a default value equal to your version, ie: "1.10"

Why you don't need that when minor version is < 10 is probably a bug in scrobj.dll (which implements WSH parsing). I suspect the code 1) uses default 1.something value when nothing is specified and 2) is broken when the major or minor versions are hexa. Here is a dump of the function that does it, it uses strtoul with a 10 radix/base instead of 16:

enter image description here

2
  • Thanks for your answer. Though note that it's not that it works for versions that have the same decimal and hexadecimal representation (1.0 through 1.9). It seems to work for any version whose hexadecimal representation do not contain any af digits (as my question says). So it works for 1.0 or 1.9, does not work for 1.10 or 1.15, but works again for 1.16 or 1.25, and does not work again for 1.26 (I didn't check all intermediate values, but the pattern seems to be clear). Commented Aug 4, 2022 at 16:48
  • Yes, it's really bugged :-) Commented Aug 4, 2022 at 17:02
1

As @Simon wrote in his answer, the problem can be worked around by creating Version key.

This updated .NET C# registration code does it:

using Microsoft.Win32;
using System;
using System.Reflection;
using System.Runtime.InteropServices;

[assembly: Guid("D766F0C8-9968-4A72-B918-D2780AEE7399")]

[assembly: AssemblyVersion("1.20.0.0")]

namespace netregasm
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDispatch)]
    [Guid("F6FAF39E-8E51-443B-9170-72D63A6E9A6B")]
    public sealed class TheClass
    {
        public TheClass()
        {
        }

        public int Test()
        {
            return 5;
        }

        [ComRegisterFunction]
        private static void ComRegister(Type t)
        {
            Assembly assembly = Assembly.GetAssembly(t);

            string guid = Marshal.GetTypeLibGuidForAssembly(assembly).ToString();

            int major, minor;
            Marshal.GetTypeLibVersionForAssembly(assembly, out major, out minor);
            string version = $"{major}.{minor}";

            RegistryKey root = Registry.ClassesRoot;
            root.CreateSubKey(GetTypeLibKey(t)).SetValue(null, guid);
            root.CreateSubKey(GetVersionKey(t)).SetValue(null, version);
        }

        [ComUnregisterFunction]
        private static void ComUnregister(Type t)
        {
            RegistryKey root = Registry.ClassesRoot;
            root.DeleteSubKey(GetTypeLibKey(t), false);
            root.DeleteSubKey(GetVersionKey(t), false);
        }

        private static string GetClsidKey(Type t)
        {
            return "CLSID\\" + t.GUID.ToString("B").ToUpperInvariant();
        }

        private static string GetTypeLibKey(Type t)
        {
            return GetClsidKey(t) + "\\TypeLib";
        }

        private static string GetVersionKey(Type t)
        {
            return GetClsidKey(t) + "\\Version";
        }
    }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.