Enumerating Managed Processes

I noticed Brad Abrams and Krzysztof Cwalina were  blogging about how to find a list of all the managed processes on a machine.

Their solution involved looking for managed perf counters. The debugging APIs provide a more orthodox approach via the ICorPublish API (see corpub.idl in the SDK). ICorPublish is an unmanaged com-classic API, but MDbg contains managed wrappers for it.

 

Here’s a simple C# test app to demo this:

using System;

using Microsoft.Samples.Debugging.CorPublish; // Managed wrappers for ICorPublish

class Program

{

    public static void Main()

    {

        CorPublish cp = new CorPublish();

        Console.WriteLine("Active processes on current maschine:");

        foreach (CorPublishProcess cpp in cp.EnumProcesses())

        {

            Console.WriteLine("(PID: " + cpp.ProcessId + ") " + cpp.DisplayName);

        }

    }

}

You need to link it against corapi.dll and corapi2.dll from the MDbg beta 1 sample.

 

 

The “processenum” command in MDbg and the  “pro” command in Cordbg use this API to list all managed processes. This is particularly useful funcitonality when you want to attach to a running managed process. So it’s no surprise that Visual Studio’s Attach dialog has similar functionality.

 

 

Here’s the code for MDbg’s “processenum” command. (In BuiltInCommands.cs in the MDbg beta 1 sample). I used this to write the demo app above.

        [

         CommandDescription(

           CommandName="processenum",

           MinimumAbbrev=3,

           ResourceManagerKey=typeof(BuiltInCommands)

         )

        ]

        public static void ProcessEnumCmd(string arguments)

        {

            CorPublish cp = new CorPublish();

            WriteOutput("Active processes on current maschine:");

            foreach(CorPublishProcess cpp in cp.EnumProcesses())

            {

                if(Process.GetCurrentProcess().Id!=cpp.ProcessId) // let's hide our process

                {

                    WriteOutput("(PID: "+cpp.ProcessId+") "+cpp.DisplayName);

                    foreach(CorPublishAppDomain cpad in cpp.EnumAppDomains())

                    {

                        WriteOutput("\t(ID: "+cpad.Id+") "+cpad.Name);

                    }

                }

            }

        }

 

 

 

 

Here’s the equivalent code from Cordbg’s “pro” command.  (This is from the Cordbg sample in the V1.1 SDK in $\SDK\v1.1\Tool Developers Guide\Samples\debugger\Commands.cpp)

    virtual void Do(DebuggerShell *shell, ICorDebug *cor, const WCHAR *args)

    {

        BOOL fPidSpecified = TRUE;

        int ulPid;

        if (!shell->GetIntArg(args, ulPid))

            fPidSpecified = FALSE;

       

        ICorPublish *pPublish;

        HRESULT hr = ::CoCreateInstance (CLSID_CorpubPublish,

                                        NULL,

                                        CLSCTX_INPROC_SERVER,

   IID_ICorPublish,

                                        (LPVOID *)&pPublish);

        if (SUCCEEDED (hr))

        {

            ICorPublishProcessEnum *pProcessEnum = NULL;

            ICorPublishProcess *pProcess [1];

            BOOL fAtleastOne = FALSE;

            if (fPidSpecified == FALSE)

            {

                hr = pPublish->EnumProcesses (COR_PUB_MANAGEDONLY,

                                                &pProcessEnum);

            }

            else

            {

                hr = pPublish->GetProcess (ulPid,

                                           pProcess);

            }

            if (SUCCEEDED (hr))

            {

                ULONG ulElemsFetched;

  if (fPidSpecified == FALSE)

                {

                    pProcessEnum->Next (1, pProcess, &ulElemsFetched);

                }

                else

                {

                    ulElemsFetched = 1;

                }

         while (ulElemsFetched != 0)

                {

                    UINT pid;

                    WCHAR szName [64];

                    ULONG32 ulNameLength;

                    BOOL fIsManaged;

                    pProcess [0]->GetProcessID (&pid);

                    pProcess [0]->GetDisplayName (64, &ulNameLength, szName);

                    pProcess [0]->IsManaged (&fIsManaged);

                    if ((fPidSpecified == FALSE) || (pid == ulPid))

                    {

                        shell->Write (L"\nPID=0x%x (%d) Name=%s\n", pid, pid, szName);

                        fAtleastOne = TRUE;

                        ICorPublishAppDomainEnum *pAppDomainEnum;

                        hr = pProcess [0]->EnumAppDomains (&pAppDomainEnum);

                        if (SUCCEEDED (hr))

                        {

                            ICorPublishAppDomain *pAppDomain [1];

                            ULONG ulAppDomainsFetched;

                            pAppDomainEnum->Next (1, pAppDomain, &ulAppDomainsFetched);

                            while (ulAppDomainsFetched != 0)

                            {

                                ULONG32 uId;

                                WCHAR szName [64];

               ULONG32 ulNameLength;

                                pAppDomain [0]->GetID (&uId);

                                pAppDomain [0]->GetName (64, &ulNameLength, szName);

                                shell->Write (L"\tID=%d AppDomainName=%s\n", uId, szName);

                                pAppDomain [0]->Release();

                                pAppDomainEnum->Next (1, pAppDomain, &ulAppDomainsFetched);

                            }

                        }

    }

                    pProcess [0]->Release();

                    if (fPidSpecified == FALSE)

                    {

                        pProcessEnum->Next (1, pProcess, &ulElemsFetched);

                    }

                    else

                    {

                        ulElemsFetched--;

                    }

                }

                if (!fAtleastOne)

                {

                    if (fPidSpecified)

                        shell->Error (L"No managed process with given ProcessId found\n");

                    else

                        shell->Error (L"No managed process found\n");

                }

            }

            if (pProcessEnum != NULL)

                pProcessEnum->Release();

        }

    }

 

 

As a little tangent, I find it interesting that the unmanaged code is about 120 lines and the managed goo is only about 20 lines. And they do the same thing. I love managed code.