Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C++
Article

RegSvrEx - An Enchanced COM Server Registration Utility

Rate me:
Please Sign up or sign in to vote.
4.90/5 (38 votes)
12 Jan 2003CPOL7 min read 237.4K   6.9K   55   34
This is a tool similar to RegSvr32, but it allows for registration of COM servers for the current user only.

Introduction

This utility (RegSvrEx) is similar to RegSvr32 but with certain extra functionality. The extra features which this utility offers are :-

  1. Current user only registration for COM servers (more about this later)
  2. Registration of executable files (though this is not the primary feature)

The utility is meant to work only on Windows 2000, XP and Windows Server 2003, as current user registration is a feature available only on these operating systems.

Background

It all started when I felt the need for a tool that could :-

  1. Register COM servers only for the current user
  2. Create a .reg file instead of registering the COM server. I needed this for creating a windows installer package.

I found an answer for the second problem in the tool RegCap.exe which comes with Microsoft Visual Studio.NET. This tool can be found in the sub directory - \Common7\Tools\Deployment within the main installation directory of Visual Studio.NET. If you want to create a .reg file instead of registering a COM server (dll or an exe), you can simply invoke this tool from the command prompt as:-

RegCap /O OutputFile.reg MyComServer.dll

This will output all the registration entries of MyCOMServer.dll to OutputFile.reg without actually registering the COM server. This is very useful in creating windows installer packages where the best practice is not to self register a COM server. This tool is internally used by the IDE when building the setup projects.

About HKEY_CLASSES_ROOT in Windows 2000 and above

Windows 2000 and above versions of Windows provide a merged view of HKEY_CLASSES_ROOT. HKEY_CLASSES_ROOT is formed by a merger of registry keys from HKEY_CURRENT_USER\Software\Classes and HKEY_LOCAL_MACHINE\Software\Classes. What this means, when a registry key HKCR\Test is queried for, if a key HKEY_CURRENT_USER\Software\Classes\Test is returned if it exists, otherwise HKEY_LOCAL_MACHINE\Software\Classes\Test is returned. But when you actually want to create a new subkey in HKEY_CLASSES_ROOT, Windows creates the subkey under HKEY_LOCAL_MACHINE\Software\Classes. For more information refer to the article in MSDN about Merged View of HKEY_CLASSES_ROOT.

RegSvr32 can only register COM servers for the entire machine and not just only for the current user - as when subkeys for COM registration info like CLSID, AppID, Interface and TypeLib are created under HKEY_CLASSES_ROOT, they will actually be created under HKEY_LOCAL_MACHINE\Software\Classes. If instead these subkeys are created under HKEY_CURRENT_USER\Software\Classes, only the current user will be able to use the COM server.

User only registration of COM servers

So what are the advantages of registering a COM server for only the current user? The principal advantage is that, the user can still register and use the COM server even if he is not an administrator of the machine or does not have write access to HKEY_LOCAL_MACHINE. This is especially useful for developers who do not logon to their machines with adminstrative privileges. I started following this practice of not logging into my machine as an administrator just recently. The main problem I faced was that I was not able to register any COM servers. To overcome this problem I needed a tool that registered COM servers using HKEY_CURRENT_USER\Software\Classes. The other advantage of this was that, I had no worries about adding lots of useless keys to the HKEY_LOCAL_MACHINE when working on temporary test projects, involving registration of COM servers.

Using the tool

The tool has been developed as a console application. Here is the syntax for using the tool:-

RegSvrEx [/u] [/c] servername
  • /u - Unregister server
  • /c - Register or unregister (if /u is also specified) only for the current user
  • servername - Full path of the server .exe or .dll

The post build step of a default ATL project created in Visual Studio.NET can be modified to use RegSvrEx instead of RegSvr32 and the post build command can read as:-

RegSvrEx /c $(TargetPath)

Same thing can be done in custom build step of Visual Studio 6.0 projects. Thus a developer no longer needs to be an administrator to test and build ATL or COM projects.

RegOverridePredefkey

When I initially started to think about developing the tool I immediately thought to use API hooking. I could hook the RegOpenKey, RegCreateKey, RegSetValue etc. functions and behind the scenes make calls to the actual functions by overriding HKEY_CLASSES_ROOT with HKEY_CURRENT_USER\Software\Classes. RegCap.exe works in this way. I was searching for a simpler way and I found the function RegOverridePredefkey. This function was introduced with Windows 2000 and its signature looks like:-

LONG RegOverridePredefKey(HKEY hKey, HKEY hNewHKey);

The first parameter hKey can be any of the predefined keys such as HKEY_CLASSES_ROOT or HKEY_LOCAL_MACHINE. The second parameter can be any open registry key. Calling this function makes the standard key an alias of the new key (the second parameter to the function). One thing to note here is that, this function only effects the current process.

DLL registration

Now that we understand the magic of RegOverridePredefkey lets see how the function is used in the code. Here is a snippet of the code that registers a DLL.

HRESULT RegisterDll(LPCWSTR szDll, bool bUnregister, bool bCurrentUser)
{
    LPCSTR szFunction = 
            bUnregister ? "DllUnregisterServer" : "DllRegisterServer";
    HRESULT hr = S_OK;

    HMODULE hMod = LoadLibrary(szDll);

    if (hMod != NULL)
    {
        typedef HRESULT (_stdcall *DLLPROC)();
        DLLPROC pfnDllProc = reinterpret_cast<DLLPROC>
                            (GetProcAddress(hMod, szFunction));

        if (pfnDllProc)
        {
            if (bCurrentUser)
            {
                //Override HKEY_CLASSES_ROOT
                hr = OverrideClassesRoot(HKEY_CURRENT_USER, 
                                        L"Software\\Classes");
            }

            if (SUCCEEDED(hr))
                hr = (*pfnDllProc)();
        }
        else
            hr = GetHresultFromWin32();

        FreeLibrary(hMod);
    }
    else
        hr = GetHresultFromWin32();
        
    return hr;
}

The function RegisterDll takes 3 parameters as described:-

  • szDll - The file name of the DLL
  • bUnregister - If true, the DLL needs to be unregistered otherwise it needs to be registered
  • bCurrentUser - If true it needs to be registered only for the current user, otherwise it needs to be registered for all users

The code is pretty straight forward.

  1. Find the appropriate DLL Entry export - DllRegisterServer or DllUnregisterServer based on the bUnregister parameter.
  2. If the DLL needs to be registered or unregistered for the current user, override the HKEY_CLASSES_ROOT with HKEY_CURRENT_USER\Software\Classes.
  3. Finally call the appropriate exported function

Exe registration

Registering an executable is not as straight forward. In order to register an executable it needs to be invoked with /RegServer command line and in order to unregister it it needs to be invoked with /Unregserver command line. The problem here is RegOverridePredefkey which did the magic for us when registering DLLs, has to be called from within the process of the executable that needs to be registered. So somehow we need to inject code inside the target process, to call RegOverridePredefkey. The easiest way is to inject a DLL which calls RegOverridePredefkey in its DllMain entry point. This needs to be done before the start of the process. Luckily, CreateProcess function allows for creation of a process in suspended mode. In suspended mode the system sets the CPU instruction pointer at the start of the process (_BaseProcessStartThunk in kernel32.dll) but does not actually start the process. Here is a code snippet of how the executable process is started.

HRESULT RegisterExe(LPCWSTR szExe, bool bUnregister, bool bCurrentUser)
{
    LPWSTR szCmdLine =  bUnregister ? L"/UnregServer" : L"/RegServer";

    STARTUPINFO si = {0};
    si.cb = sizeof(STARTUPINFO);
    si.wShowWindow = SW_SHOWDEFAULT;

    PROCESS_INFORMATION pi = {0};
    
    HRESULT hr = S_OK;

    BOOL b = CreateProcess(szExe, szCmdLine, NULL, NULL, 
                FALSE, CREATE_SUSPENDED , NULL, NULL, &si, &pi);

The WIN32 API functions GetThreadContext and SetThreadContext allows for modification of a thread's CPU registers. So using these functions, it is possible to modify even the instruction pointer register - EIP. We can place the instruction pointer at a different locations, where we can place some code that loads a DLL which calls RegOverridePredefkey. Here is the code that does it:-

//If the registration is required for the 
//current user inject code to override the keys
if (bCurrentUser)
{
    CONTEXT ctxt = {0};
    ctxt.ContextFlags = CONTEXT_FULL;
    GetThreadContext(pi.hThread, &ctxt);
    
    LPVOID pvDestAddr = VirtualAllocEx(pi.hProcess, NULL, 
                            sizeof(LoadLibraryCode), 
                            MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    
    DWORD dwDestAddr = PtrToUlong(pvDestAddr);
    LoadLibraryCode code(ctxt.Eip, dwDestAddr);
    
    WriteProcessMemory(pi.hProcess, pvDestAddr, 
            &code, sizeof(LoadLibraryCode), NULL);
    
    ctxt.Eip = code.GetRemoteCodeAddr(dwDestAddr);
    SetThreadContext(pi.hThread, &ctxt);
}

ResumeThread(pi.hThread);
CloseHandle(pi.hThread);

WaitForSingleObject(pi.hProcess, INFINITE);

DWORD dwResult = 0;
GetExitCodeProcess(pi.hProcess, &dwResult);

hr = GetHresultFromWin32(dwResult);

CloseHandle(pi.hProcess);

The structure LoadLibraryCode has machine language intructions, which can load a DLL. Here is how the structure looks like:

#pragma pack(1)

struct LoadLibraryCode
{
    WCHAR m_szLibPath[_MAX_PATH + 1];

    //Save registers
    BYTE m_pushEAX;
    BYTE m_pushECX;
    BYTE m_pushEDX;
    //push m_szLibPath
    BYTE m_push;
    DWORD m_dwAddrLibPath; //Address of lib path in the traget process
    //call LoadLibraryW
    BYTE m_call;
    DWORD m_dwRelAddrLoadLibraryW;
    BYTE m_popEDX;
    BYTE m_popECX;
    BYTE m_popEAX;
    BYTE m_jmp;
    DWORD_PTR m_dwRelAddr; //jump back to original address

    LoadLibraryCode(DWORD dwAddrToJump, DWORD dwRemoteAddrOfThis)
    {
        ::GetModuleFileNameW(NULL, m_szLibPath, _MAX_PATH + 1);
        lstrcpyW(::PathFindFileNameW(m_szLibPath), L"RegInDll.Dll");

        m_pushEAX = 0x50; //               push        eax  
        m_pushECX = 0x51; //               push        ecx  
        m_pushEDX = 0x52; //               push        edx  
        m_push = 0x68;
        m_dwAddrLibPath = dwRemoteAddrOfThis;
        m_call = 0xE8;      //               call

        DWORD dwAddrLoadLibraryW = PtrToUlong(GetProcAddress
                                    (GetModuleHandle(L"kernel32.dll"), 
                                    "LoadLibraryW"));
        m_dwRelAddrLoadLibraryW = dwAddrLoadLibraryW - 
                                    (dwRemoteAddrOfThis + 
                                    ((BYTE*)&&m_dwRelAddrLoadLibraryW 
                                    - (BYTE*)this) + sizeof(DWORD));

        m_popEDX = 0x5A;  //               pop         edx  
        m_popECX = 0x59;  //               pop         ecx  
        m_popEAX = 0x58;  //               pop         eax  
        m_jmp = 0xE9;     //               jmp
        m_dwRelAddr = dwAddrToJump - 
                        (dwRemoteAddrOfThis + sizeof(LoadLibraryCode)) ;
    }

    DWORD GetRemoteCodeAddr(DWORD dwRemoteAddr)
    {
        return dwRemoteAddr + ((BYTE*)&m_pushEAX - (BYTE*)this);
    }
};

#pragma pack(pop)

In order to get the machine language instruction codes, I actually wrote the code in assembly and compiled it and observed the assembly listing to see the actual machine instruction byte values (I could have of course looked at the Pentium instruction manual to find that out). The assembly language looks like following:-

;Save the registers
push EAX
push ECX
push EDX
push <address of the string containing the path of the library>
call LoadLibraryW
pop EDX
pop ECX
pop EAX
jmp <relative address of the process start point>

This is a very standard code and technique used to inject DLLs in a process before the process starts. The DllMain of the RegInDll.dll which is injected into the target process looks like:-

BOOL APIENTRY DllMain( HMODULE hModule, 
        DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hModule);

        //Failed to override the key for 
        //some reason so just terminate the process
        LONG l = OverrideClassesRoot(HKEY_CURRENT_USER, 
                                    L"Software\\Classes");

        if (l != ERROR_SUCCESS)
            ExitProcess(l);
    }

    return TRUE;
}

Thus at the start of the process HKEY_CLASSES_ROOT is overridden and the registration takes place according to our wishes, without actually requiring to modify the source code of the target application.

Conclusion

This utility works in most cases but there are some times when it will not work. One such case might be when the registration code requires to read an entry from the HKEY_CLASSES_ROOT and the entry is not in HKEY_CURRENT_USER\Software\Classes. In that case the registration may fail. As usual, any suggestions for improvement are welcome.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralNo need for machine code Pin
Phil Wilson9-Jun-08 9:28
Phil Wilson9-Jun-08 9:28 
GeneralRe: No need for machine code Pin
Rama Krishna Vavilala9-Jun-08 9:36
Rama Krishna Vavilala9-Jun-08 9:36 
GeneralRe: No need for machine code Pin
Phil Wilson9-Jun-08 9:51
Phil Wilson9-Jun-08 9:51 
GeneralF'n right!!! Pin
Member 60417918-Feb-08 10:00
Member 60417918-Feb-08 10:00 
QuestionPlease help... Pin
Shefeeq Abubakr4-Dec-07 1:00
sussShefeeq Abubakr4-Dec-07 1:00 
GeneralKey-Overriding Affects Registration Pin
Serge Baltic26-Nov-07 11:39
Serge Baltic26-Nov-07 11:39 
QuestionWhat about Vista? Pin
horvat@pisem.net1-Feb-07 22:29
horvat@pisem.net1-Feb-07 22:29 
AnswerRe: What about Vista? Pin
Rama Krishna Vavilala2-Mar-07 0:30
Rama Krishna Vavilala2-Mar-07 0:30 
GeneralRe: What about Vista? Pin
modicr8-Jun-07 13:50
modicr8-Jun-07 13:50 
GeneralRe: What about Vista? Pin
verasen11-Jul-07 6:36
verasen11-Jul-07 6:36 
GeneralRe: What about Vista? Pin
propellaAdam1-Oct-07 16:43
propellaAdam1-Oct-07 16:43 
GeneralThere is something I can't understand Pin
Programming is art29-Nov-06 13:50
Programming is art29-Nov-06 13:50 
GeneralGreat ! Pin
hazzus217-Sep-06 3:30
hazzus217-Sep-06 3:30 
GeneralExcellent !!!! This is exactly what I need.. Pin
Thierry Maurel15-May-06 23:39
Thierry Maurel15-May-06 23:39 
QuestionCan you can call CoInitialize before Pin
willyboy123422-Jun-05 5:41
willyboy123422-Jun-05 5:41 
AnswerRe: Can you can call CoInitialize before Pin
spam2@vbusers.com21-Aug-06 1:57
spam2@vbusers.com21-Aug-06 1:57 
GeneralRe: Can you can call CoInitialize before Pin
willyboy123421-Aug-06 23:36
willyboy123421-Aug-06 23:36 
GeneralUnspecified error Pin
S. Weisiger20-May-05 8:34
S. Weisiger20-May-05 8:34 
GeneralRe: Unspecified error Pin
comerror4-Oct-06 1:28
comerror4-Oct-06 1:28 
QuestionReg file - hardcoded paths? Pin
noirs217-May-05 4:02
noirs217-May-05 4:02 
GeneralI did something very similar! Pin
PhilWilson22-Apr-04 5:37
PhilWilson22-Apr-04 5:37 
GeneralWin9x Pin
Anthony_Yio25-Mar-04 23:05
Anthony_Yio25-Mar-04 23:05 
QuestionWhy so late? Pin
Paul Selormey13-Jan-03 22:34
Paul Selormey13-Jan-03 22:34 
AnswerRe: Why so late? Pin
Rama Krishna Vavilala14-Jan-03 7:11
Rama Krishna Vavilala14-Jan-03 7:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.