Call a .NET class from a Gadget (without regasm.exe)

In some cases, a gadget developer would like to interact with the "world" outside of the sidebar such as libraries or applications to process some work. When it is a library, we already know that we are able to interact with COM component by using the ActiveXObject constructor (example: var xmldoc = new ActiveXObject("Microsoft.XMLDOM");).

They are also different ways to interoperate with an application or a web service using WCF like described by Karsten Januszewski on his blog

Or it is also possible to reference a Windows Forms UserControl in the OBJECT tag and CLASSID parameter using url#type where type is a fulltype name (namespace + class) in .NET. For an example, follow the link. But that will only work using http: in the url and it means that it will not work when the .NET assembly is local.

Another way would be to dynamically execute a command line application, pass it arguments and get back the standard output of the application to get some kind of processing result.

For our current solution, we will focus on the first described solution aka interoperate with a .NET assembly exposed as a COM component. Actually, this solution requires to register the .NET assembly using regasm.exe command. The solution described here bypass the use of the command and simulates the behavior of regasm.exe directly from javascript in the Gadget. 

So first, you need to have a .NET assembly exposed as a COM component (like described on Phil Wilson's blog). Then to simulate the regasm behavio, we need to have a list of registry entries that are added when registering a .NET assembly. The default behavior of regasm is to create registry entries in HKCR - HKEY_CLASSES_ROOT which won't fit a public deployment as many users are not admin of their machines. But what we know is that a user can modify registry entries under HKCR - HKEY_CURRENT_USER.

So first, we need to generate a .reg file using regasm.exe. For example, regasm.exe /regfile /codebase yourassembly.dll:

REGEDIT4

[HKEY_CLASSES_ROOT\DPAPIComLib.DPAPIUserScopeCcw]
@="MTC.Security.DPAPIUserScopeCcw"

[HKEY_CLASSES_ROOT\DPAPIComLib.DPAPIUserScopeCcw\CLSID]
@="{C76D54A7-8472-44C7-AD7F-BA394E1D3900}"

[HKEY_CLASSES_ROOT\CLSID\{C76D54A7-8472-44C7-AD7F-BA394E1D3900}]
@="MTC.Security.DPAPIUserScopeCcw"

[HKEY_CLASSES_ROOT\CLSID\{C76D54A7-8472-44C7-AD7F-BA394E1D3900}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="MTC.Security.DPAPIUserScopeCcw"
"Assembly"="DPAPIComLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3def732129791e7a"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///C:/Users/fredeq/AppData/Local/Microsoft/Windows Sidebar/Gadgets/mydotnet[1].gadget/bin/DPAPIComLib.dll"

[HKEY_CLASSES_ROOT\CLSID\{C76D54A7-8472-44C7-AD7F-BA394E1D3900}\InprocServer32\1.0.0.0]
"Class"="MTC.Security.DPAPIUserScopeCcw"
"Assembly"="DPAPIComLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3def732129791e7a"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///C:/Users/fredeq/AppData/Local/Microsoft/Windows Sidebar/Gadgets/mydotnet[1].gadget/bin/DPAPIComLib.dll"

[HKEY_CLASSES_ROOT\CLSID\{C76D54A7-8472-44C7-AD7F-BA394E1D3900}\ProgId]
@="DPAPIComLib.DPAPIUserScopeCcw"

[HKEY_CLASSES_ROOT\CLSID\{C76D54A7-8472-44C7-AD7F-BA394E1D3900}\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]

Then, replace all HKEY_CLASSES_ROOT occurences by HKEY_CURRENT_USER\Software\Classes to be able to register your .NET assembly in the current user registry hive. Take a look at MSDN to have a detailed article on these deployment principles.

So now, we are at a stage where we know how to expose a .NET assembly as a COM Component, we know how to register a COM clsid/progid for a user without admin privileges, and finally we know how to update the registry using the WScrippt.Shell COM Component. So everything is here to dynamically register a .NET Assembly from a Sidebar Gadget.

Take a look at the following code (should be implemented/referenced in your Gadget):

function regAsm(root, progId, cls, clsid, assembly, version, codebase) {
var wshShell;
wshShell = new ActiveXObject("WScript.Shell");

//wshShell.RegWrite(root + "\\Software\\Classes\\", progId);
wshShell.RegWrite(root + "\\Software\\Classes\\" + progId + "\\", cls);
wshShell.RegWrite(root + "\\Software\\Classes\\" + progId + "\\CLSID\\", clsid);
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\", cls);

wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\", "mscoree.dll");
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\ThreadingModel", "Both");
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\Class", cls);
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\Assembly", assembly);
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\RuntimeVersion", "v2.0.50727");
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\CodeBase", codebase);

wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\" + version + "\\Class", cls);
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\" + version + "\\Assembly", assembly);
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\" + version + "\\RuntimeVersion", "v2.0.50727");
wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\InprocServer32\\" + version + "\\CodeBase", codebase);

wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\ProgId\\", progId);

wshShell.RegWrite(root + "\\Software\\Classes\\CLSID\\" + clsid + "\\Implemented Categories\\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}\\", "");
}

Call this function using your own values (clsid, progid...). You find your values in the .reg file generated by regasm.exe:

function button1_click()
{
regAsm("HKCU",
"DPAPIComLib.DPAPIUserScopeCcw",
"MTC.Security.DPAPIUserScopeCcw",
"{C76D54A7-8472-44C7-AD7F-BA394E1D3900}",
"DPAPIComLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3def732129791e7a",
"1.0.0.0",
System.Gadget.path + "/bin/DPAPIComLib.dll");
}

You may want to replace HKCU by HKLM when user is running the gadget as an administrator.

Then, after using regAsm funtion, you are able to call your .NET Class from the javascript as a COM Component like you do when using ActiveXObject.

...var

sec;

sec =

new ActiveXObject("DPAPIComLib.DPAPIUserScopeCcw");

out.value= sec.Protect(

"Hello", "World");...

This is a first explanation of how to call .NET from a Gadget without registration issues. I will refine the information based on readers feedback and had some features like a tool/VS add-in to generate the regAsm call (the .js file) during development process based on reflection of the compiled assembly to be registered from Javascript.

Comments

  • Anonymous
    January 23, 2007
    Very useful article, many thanks! I've just one problem, if I use regAsm with HKCU, I can't use my registered COM object. Using HKLM the ActiveXObject creation works fine.

  • Anonymous
    March 21, 2007
    This code will behave differently if your are Admin or not. If Admin is logged, HKLM will work fine. If not it will work with HKCU. This code is a sample and should be improved to support HKLM/HKCU cases (try catch on HKLM if catch error then HKCU) and add feature to unregister the .NET assembly. I would definitly reference the solution posted by someone else who has more time than me on this subject :-).

  • Anonymous
    March 29, 2007
    Great article! But I have a new scenario: think about a gadget calling a .NET COM (as we saw here) but a .NET COM starting a remoting TCP, using another .NET Lib. It seems like the second LIB isn't loaded when you call it by ActiveXObject. Could be?

  • Anonymous
    June 13, 2007
    MSDN Canada runs a Gadget VS Gadget competition . Create a great Windows Vista Gadget and you have the

  • Anonymous
    June 06, 2011
    Can I use this with ASP.NET page to call .Net assembly? Also will it support firefox and chrome browser?