Registering .Net COM DLLs without Admin rights / regasm
Asked Answered
L

3

27

Recently, I wrote a class library in C# for use in Office applications, including a critical Access application used by ~70 people. For users with admin rights, registering the DLL is trivial, but getting the DLL working on other machines was problematic.

Registering the DLL for use with admin privileges

  • Build the DLL in Visual Studio. Ensure these options are selected on the project's Application tab:
    • Output Type: Class Library
    • Assembly Information: Make assembly COM-visible: Checked
  • Use an elevated command prompt to register the assembly:
    • RegAsm YourDll.dll /tlb /codebase
  • Add a reference to YourDll.tlb in VBA: Tools-->References
  • Verify that you can create an instance of your object

What is happening?

Regasm is doing several things here. First, it is creating a type library (YourDLL.tlb) which provides information about the classes that are in your DLL. Second, it is storing the information about the library in the registry so that the system "knows" what you mean when you ask for a class to be instantiated as an object.

To see what registry entries Regasm is adding, you can run it with the /regfile parameter:

Regasm YourDLL.dll /codebase /regfile

(The /regfile option is invalid with the /tlb parameter.)

If the /codebase option tells Regasm to include information about where YourDLL.dll is located on disk, which is important for creating the objects.

If you open YourDLL.reg in a text editor, you'll see the entries that Regasm is adding to the registry: entries to HKEY_Classes_Root (which is really just a redirect to HKLM\Software\Classes). Unfortunately, you need admin privileges to modify HKLM, so this isn't going to work for our other users.

There are a few other threads (e.g. Registering a COM without Admin rights, Register COM DLL for use by VBA, Register DLL (ActiveX) for non-admin user, Unable to register .NET COM DLL, COM Interop without regasm) that discuss the problem, but their solutions are complex (e.g. require registry redirection) or incomplete (assume you already know at least half of the answer) or do not work in mixed 64/32 bit environments (e.g. Win64, Office32).

So how do you register a COM DLL, created in Visual Studio, for use in VBA 32 and 64 bit environments for the current user, without administrative privileges?

Lifeanddeath answered 12/5, 2016 at 17:9 Comment(0)
L
18

Setting up the registry files

To register the components for use in 32 and 64 bit environments, we are going to need to modify the registry file we created in our test case. Open it with your favorite text editor. The entries should look something like this:

[HKEY_CLASSES_ROOT\\YourAssembly.Class]
@="YourAssembly.Class"

[HKEY_CLASSES_ROOT\\YourAssembly.Class\\CLSID]
@="{YourClassGUID}"

Make sure that it includes "CodeBase"= entries.

Do a global find/replace:

  • Change HKEY_CLASSES_ROOT (which is aliasing to HKLM\Software\Classes)
  • To HKEY_CURRENT_USER\Software\Classes

Copy all of the registry keys (and their values, which are listed below the keys) to a second text file. In the second text file:

  • Delete all of the keys (and their related values) that do not contain \CLSID\
  • Do a global find/replace:
    • Change Classes\CLSID
    • To: Classes\Wow6432Node\CLSID

Copy all of the keys from the second text file to your original .reg file, and save it.

Remove the registration entries from HKLM by using regasm:

Regasm YourDLL.dll /unregister

Make sure things don't work

In order to make sure that our change worked (and that you're not just successful because of the registration you did with regasm originally), we need to make sure that VBA cannot create the object right now.

Open up your favorite VBA application, and add a reference to YourDLL.tlb. Create a new procedure that is something like this:

Public Sub TestYourDLL()
  Dim x as AssemblyName.ClassName
  Set x = New AssemblyName.ClassName
  Debug.Print "Apparently this worked."
End Sub

Run TestYourDLL. You should receive the error:

Run-time error '429':

ActiveX component can't create object

(If you don't receive the error, your DLL is still registered.)

Save and exit your VBA application.

Making sure they work

Now, run the YourDLL.reg that you created earlier to import the entries to the registry. (If you get an "Access denied" message, you forgot to change from HKEY_CLASSES_ROOT to HKEY_CURRENT_USER\Software\Classes)

Open your VBA application again, and run TestYourDLL. You should now see "Apparently this worked." in your immediate window. Congratulations! You've registered the DLL! (If you get an "Automation error: The system cannot find the file specified"-type error, your registry file is either missing the Codebase entries, or they are not pointing to the actual location of your DLL.)

Additional steps

In my case, I'm going to be installing the DLL on a bunch of other users' computers alongside my application, so at installation time I'll update the CodeBase value to refer to the location where I'm installing the DLL, and I'll also install the registry entries through code, rather than by executing the .reg file. But, now that I know the required entries, doing that is trivial.

Lifeanddeath answered 12/5, 2016 at 17:9 Comment(6)
I tried a similar process some time ago and it did not work for me, because the generated reg file did not contain all the necessary entries. This might not affect you, but you should be aware of this limitation...Redundant
@PetrSr: my experience was that the only entries that were missing entirely with regasm /regfile were the CodeBase entries, but regasm /codebase /regfile fixed that. (Of course, the 32 bit registration was also missing, which is why I had to create those entries myself, using the basic info as a base.) Were you able to figure out which entries were missing for you?Lifeanddeath
The TypeLib and Interface IDs were missing and I was not able to get my DLL to work without them. As described in regasm documentation, these entries will always be missing with the /regfile option. I tried to harvest the missing etnries directly from the registry, but it proved to be a rather tedious task. I eventually gave up and asked my boss to register the DLL using his admin account for me... :-)Redundant
@PetrSr That would be really tedious. My test case didn't involve interfaces, so I missed that. I'll see if I can figure out how the interface and type library IDs are registered and add that info if I can.Lifeanddeath
For TypeLib, you need to call RegisterTypeLibForUser on the tlb file. See regasm source code with dotPeek (free tool). Regasm uses RegisterTypeLib that requires admin rights.Dipetalous
i also didn't get this to work 1st time round, but then read thro the code on @Erik A's answer. that sorted it for me in terms of the required 64 bit class reference. i.e. remove Wow6432Node from the definition of targetting 64 bitIamb
J
8

C White's answer is great if you can do it manually on every computer.

For automatically adding the necessary registry entries, I use the following code. It accounts for 32-bits Office on 64-bits Windows, and can be cleaned up afterwards.

Public Sub RegisterDLL(DLLPath As String)
    Dim RegasmPath As String
    RegasmPath = "C:\Windows\Microsoft.NET\Framework\" & Dir("C:\Windows\Microsoft.NET\Framework\v4*", vbDirectory) & "\RegAsm.exe" 'Path to RegAsm, adjust to the version of the .Net framework you're targeting

    #If Win64 Then
        Const Win64 = True
    #Else
        Const Win64 = False
    #End If
    Dim LibraryPath As String
    LibraryPath = Left(DLLPath, Len(DLLPath) - 4)
    If Dir(DLLPath) = "" Then 'Check if file exists
        Err.Raise 1, Description:="Missing DLL!"
        Exit Sub
    End If
    CreateObject("WScript.Shell").Run """" & RegasmPath & """ """ & DLLPath & """ /codebase /regfile", 0, True 'Create .reg file using RegAsm
    Dim strNewRegPath As String
    If Not Win64 And Environ$("ProgramW6432") <> vbNullString Then
        strNewRegPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\Wow6432Node" '32 bits Office on Win 64
    Else
        strNewRegPath = "HKEY_CURRENT_USER\SOFTWARE\Classes" 'Default registry path
    End If
    Dim strRegFileContent As String
    Dim fileNo As Integer
    fileNo = FreeFile
    Open LibraryPath & ".reg" For Binary Access Read Write As #fileNo
        strRegFileContent = String(LOF(fileNo), vbNullChar)
        Get #fileNo, , strRegFileContent 'Read reg contents
        strRegFileContent = Replace(strRegFileContent, "HKEY_CLASSES_ROOT", strNewRegPath) 'Change reg path
        Put #fileNo, 1, strRegFileContent 'Overwrite, always extends so no need to truncate
    Close #fileNo
    CreateObject("WScript.Shell").Run "regedit.exe /s """ & LibraryPath & ".reg""", 0, True  'Merge silently into registry
    Kill LibraryPath & ".reg" 'Clean up registry
End Sub

Just use RegisterDLL "C:\Path\To\File.DLL" to automatically add the required entries.

To clean up when uninstalling, you can use the following:

Public Sub UnregisterDLL(DLLPath As String)
    Dim RegasmPath As String
    RegasmPath = "C:\Windows\Microsoft.NET\Framework\" & Dir("C:\Windows\Microsoft.NET\Framework\v4*", vbDirectory) & "\RegAsm.exe" 'Path to RegAsm, adjust to the version of the .Net framework you're targeting
    #If Win64 Then
        Const Win64 = True
    #Else
        Const Win64 = False
    #End If
    Dim LibraryPath As String
    LibraryPath = Left(DLLPath, Len(DLLPath) - 4)
    If Dir(DLLPath) = "" Then 'Check if file exists
        Err.Raise 1, Description:="Missing DLL!"
        Exit Sub
    End If
    CreateObject("WScript.Shell").Run """" & RegasmPath & """ """ & DLLPath & """ /codebase /regfile", 0, True 'Create .reg file using RegAsm
    Dim strNewRegPath As String
    If Not Win64 And Environ$("ProgramW6432") <> vbNullString Then
        strNewRegPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\Wow6432Node" '32 bits Office on Win 64
    Else
        strNewRegPath = "HKEY_CURRENT_USER\SOFTWARE\Classes" 'Default registry path
    End If
    Dim strRegFileContent As String
    Dim fileNo As Integer
    fileNo = FreeFile
    Dim fileOutput As Integer
    fileOutput = FreeFile + 1
    Open LibraryPath & ".reg" For Input As #fileNo
    Open LibraryPath & "remove.reg" For Output As #fileOutput
        Line Input #fileNo, strRegFileContent 'Read reg contents
        Print #fileOutput, strRegFileContent 'Copy first line blindly
        Do While Not EOF(fileNo)
            Line Input #fileNo, strRegFileContent 'Read reg contents
            strRegFileContent = Replace(strRegFileContent, "HKEY_CLASSES_ROOT", strNewRegPath) 'Change reg path
            If Left(strRegFileContent, 1) = "[" Then 'If new key
                Print #fileOutput, "[-" & Mid(strRegFileContent, 2) 'Change to remove key
            End If
        Loop
    Close #fileOutput
    Close #fileNo
    Kill LibraryPath & ".reg" 'Remove create file
    Shell "regedit.exe /s """ & LibraryPath & "remove.reg""" 'Merge silently into registry
    Kill LibraryPath & "remove.reg" 'Remove delete file
End Sub

These scripts require no references, and no admin access. They should generally run fast and allow for setting on application startup, and cleaning on application shutdown, if conflicts might be an issue.

Jerusalem answered 3/12, 2018 at 10:55 Comment(5)
between the accpted answer and your case statement for 32/64 bit, i was able to get things going -thanks for that...Iamb
Curious, could I run this script directly in the Excel file on open to load necessary DLLs which could have been bundled with it?Relief
@TekiusFanatikus Yes. I usually force a restart of the application just to be sure, but as long as you're using late binding you could use this in the same application that uses the DLL, that's exactly what I use it for.Jerusalem
@ErikA thanks! I'll investigate this further.Relief
@ErikA Great! I was finally able to register my assembly from my setup program.Argueta
G
0

I took two snapshots of the registry before and after running regasm and then duplicated it manually on vbs changed HKEY_CLASSES_ROOT to HKEY_CURRENT_USER,

works without admin and without messages UAC

in the properties on the side indicated visibility for COM and made to sign the assembly

REG_ROOT="HKEY_CURRENT_USER\Software\Classes\"
REG_GUID="{*********}"
REG_TOKEN="********"
REG_PATH="file:///*******"
REG_CLASS="***.***"
REG_NAME="***"
REG_VER="v4.0.30319"

WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\", REG_CLASS, "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\", "mscoree.dll", "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\ThreadingModel", "Both", "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\Class", REG_CLASS, "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\Assembly", REG_NAME & ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=" & REG_TOKEN, "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\RuntimeVersion", REG_VER, "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\CodeBase", REG_PATH, "REG_SZ"

WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\Class", REG_CLASS, "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\Assembly", REG_NAME & ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=" & REG_TOKEN, "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\RuntimeVersion", REG_VER, "REG_SZ"
WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\CodeBase", REG_PATH, "REG_SZ"

WS.RegWrite REG_ROOT & "CLSID\" & REG_GUID & "\ProgId\", REG_CLASS, "REG_SZ"

WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\", REG_CLASS, "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\", "mscoree.dll", "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\ThreadingModel", "Both", "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\Class", REG_CLASS, "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\Assembly", REG_NAME & ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=" & REG_TOKEN, "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\RuntimeVersion", REG_VER, "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\CodeBase", REG_PATH, "REG_SZ"

WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\Class", REG_CLASS, "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\Assembly", REG_NAME & ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=" & REG_TOKEN, "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\RuntimeVersion", REG_VER, "REG_SZ"
WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\InprocServer32\1.0.0.0\CodeBase", REG_PATH, "REG_SZ"

WS.RegWrite REG_ROOT & "WOW6432Node\CLSID\" & REG_GUID & "\ProgId\", REG_CLASS, "REG_SZ"

WS.RegWrite REG_ROOT & REG_CLASS & "\", REG_CLASS, "REG_SZ"
WS.RegWrite REG_ROOT & REG_CLASS & "\CLSID\", "" & REG_GUID & "", "REG_SZ"

Set objDLL = CreateObject(REG_CLASS)
Gilkey answered 12/7, 2022 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.