Enumerating CLR versions


The following is a sample from the developer who owns mscoree.dll. The sample prints out all the CLR versions installed in the machine.


The code will be shipped in .Net framework SDK as a sample.


 

// This is the function pointer definition for the shim API GetRequestedRuntimeInfoInfo.
// It has existed in mscoree.dll since v1.1, and in v2.0 it was modified to take “runtimeInfoFlags”
// which allow us to get even more information.
typedef HRESULT (STDAPICALLTYPE *PGetRRI)(LPCWSTR pExe,
                                          LPCWSTR pwszVersion,
                                          LPCWSTR pConfigurationFile,
                                          DWORD startupFlags,
                                          DWORD runtimeInfoFlags,
                                          LPWSTR pDirectory,
                                          DWORD dwDirectory,
                                          DWORD *dwDirectoryLength,
                                          LPWSTR pVersion,
                                          DWORD cchBuffer,
                                          DWORD* dwlength);
 
// This is the function pointer defintion for the shim API GetCorVersion.
// It has existed in mscoree.dll since v1.0, and will display the version of the runtime that is currently
// loaded into the process. If a CLR is not loaded into the process, it will load the latest version.
typedef HRESULT (STDAPICALLTYPE *PGetCV)(LPWSTR szBuffer,
                                         DWORD cchBuffer,
                                         DWORD* dwLength);
 
//————————————————————-
// PrintAllRuntimes
//
// This prints all of the runtimes installed on the machine
//————————————————————-
int PrintAllRuntimes()
{
    BOOL fV10installed = FALSE;     // Is v1.0 installed on the machine
    BOOL fV11installed = FALSE;     // Is v1.1 installed on the machine
    WCHAR wszLatestRuntime[30] = {0}; // Latest runtime on the machine
    DWORD cchLatestRuntime = 0;
    PGetRRI pfnGetRequestedRuntimeInfo = NULL;
    PGetCV pfnGetCorVersion = NULL;
    HMODULE hMscoree = NULL;
    HRESULT hr = S_OK;
 
    // First, if mscoree.dll is not found on the machine, then there aren’t any CLRs on the machine
    hMscoree = LoadLibraryA(“mscoree.dll”);
    if (hMscoree == NULL)
        goto DoneFindingRuntimes;
 
 
    // There were certain OS’s that shipped with a “placeholder” mscoree.dll. The existance of this DLL
    // doesn’t mean there are CLRs installed on the box.
    //
    // If this mscoree doesn’t have an implementation for GetCORVersion, then we know it’s one of these
    // placeholder dlls.
    pfnGetCorVersion = (PGetCV)GetProcAddress(hMscoree, “GetCORVersion”);
   
    if (pfnGetCorVersion == NULL)
        goto DoneFindingRuntimes;
 
    // Ok, so we now know that the CLR was, at one time, installed on this machine. Let’s see what versions
    // of the runtime are on the box.
 
    // v1.0 and v1.1 had an annoying habit of popping up dialogs whenever you asked for runtimes that didn’t exist.
    // We’ll surpress those dialogs with this statement.
    SetErrorMode(SEM_FAILCRITICALERRORS);
 
    // v1.1 of mscoree shipped with the API GetRequestedRuntimeInfo(). This function will help us with identifying
    // runtimes.
 
    pfnGetRequestedRuntimeInfo = (PGetRRI)GetProcAddress(hMscoree, “GetRequestedRuntimeInfo”);
 
    if (pfnGetRequestedRuntimeInfo == NULL)
    {
        // Ok, that API didn’t exist. We’ve got the v1.0 mscoree.dll on the box. We’re guaranteed that there isn’t
        // a later version of the CLR on the machine, but we’re not 100% guaranteed that v1.0 of the CLR is on the
        // box.
 
        // Unfortuately, the only way to verify that v1.0 is on the box is to try and spin up v1.0 of the CLR and
        // see if it works.
 
        WCHAR wszVersion[50];
        DWORD cchVersion = 0;
 
        hr = pfnGetCorVersion(wszVersion, NumItems(wszVersion), &cchVersion);
 
        // If this failed, then either the v1.0 CLR didn’t exist on the machine, or, if the buffer wasn’t
        // big enough to copy the version information, then something is messed up on the machine (v1.0.3705 should
        // fit in a 50 character buffer)
        if (FAILED(hr))
            goto DoneFindingRuntimes;
 
        // If the returned string is not v1.0.3705, then this machine is messed up
        if (wcscmp(wszVersion, L”v1.0.3705″))
        {
            printf(“Installation error on this machine. v1.0 of mscoree.dll is running %S of the CLR.\n”, wszVersion);
            goto DoneFindingRuntimes;
        }
 
        // Ok, we’ve verified that v1.0 is installed.
        fV10installed = TRUE;
        goto DoneFindingRuntimes;
    }
 
    // Ok, we know that, at a minimum, v1.1 of mscoree is installed on the machine. That makes this job much easier.
 
    // This function call will pop up a dialog if these runtimes don’t exist on the machine, so make sure you call
    // SetErrorMode(SEM_FAILCRITICALERRORS); as we did up above
   
    WCHAR wszVersion[50]; // The version of the runtime that satisfies the runtime request
    DWORD cchVersion = 0;
    WCHAR wszDirectory[MAX_PATH]; // The top level directory where the runtime is located. Usually is %windir%\microsoft.net\framework
    DWORD cchDirectory = 0;
 
    // Check to see if v1.0 is installed on the machine
   
    hr = pfnGetRequestedRuntimeInfo(NULL, // pExe
                   L”v1.0.3705″, // pwszVersion
                   NULL, // ConfigurationFile
                   0, // startupFlags
                   0, // v1.1, this is reserved, in v2.0, runtimeInfoFlags
                   wszDirectory, // pDirectory
                   NumItems(wszDirectory), // dwDirectory
                   &cchDirectory, // dwDirectoryLength
                   wszVersion, // pVersion
                   NumItems(wszVersion), // cchBuffer
                   &cchVersion); // dwlength
 
    if (SUCCEEDED(hr))
        fV10installed = TRUE;
 
 
    // Check to see if v1.1 is installed on the machine
    hr = pfnGetRequestedRuntimeInfo(NULL, // pExe
                   L”v1.1.4322″, // pwszVersion
                   NULL, // ConfigurationFile
                   0, // startupFlags
                   0, // v1.1, this is reserved, in v2.0, runtimeInfoFlags
                   wszDirectory, // pDirectory
                   NumItems(wszDirectory), // dwDirectory
                   &cchDirectory, // dwDirectoryLength
                   wszVersion, // pVersion
                   NumItems(wszVersion), // cchBuffer
                   &cchVersion); // dwlength
 
    if (SUCCEEDED(hr))
        fV11installed = TRUE;
 
 
    // The same thing can be done for v2.0 when the final version number of Whidbey is decided upon.
 
 
    // The v2.0 shim allows us to use flags for this function that makes it easier to use. The v1.1 mscoree.dll will
    // not allow us to call this function with 3 NULLs. However, the v2.0 mscoree.dll, along with the RUNTIME_INFO_UPGRADE_VERSION
    // flag, will return us the latest version of the CLR on the machine.
 
    hr = pfnGetRequestedRuntimeInfo(NULL, // pExe
                   NULL, // pwszVersion
                   NULL, // ConfigurationFile
                   0, // startupFlags
                   RUNTIME_INFO_UPGRADE_VERSION|RUNTIME_INFO_DONT_RETURN_DIRECTORY|RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG, // runtimeInfoFlags,
                   NULL, // pDirectory
                   0, // dwDirectory
                   NULL, // dwDirectoryLength
                   wszLatestRuntime, // pVersion
                   NumItems(wszLatestRuntime), // cchBuffer
                   &cchLatestRuntime); // dwlength
 
    // If this fails, then v2.0 of mscoree.dll was not installed on the machine.
     
DoneFindingRuntimes:
 
    printf(“Versions installed on the machine:\n”);
 
    if (fV10installed)
        printf(“v1.0.3705\n”);
 
    if (fV11installed)
        printf(“v1.1.4322\n”);
 
    if (*wszLatestRuntime)
        printf(“%S\n”, wszLatestRuntime);
 
    // If we didn’t find any runtimes
    if (!fV10installed && !fV11installed && !*wszLatestRuntime)
        printf(“<none>\n”);
 
    return 0;
}// PrintAllRuntimes


Comments (10)

  1. John Hensley says:

    Great post! Thanks for making this very useful information available. The #defines for RUNTIME_INFO_UPGRADE_VERSION, RUNTIME_INFO_UPGRADE_VERSION and RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG that are passed as the runtimeInfoFlags parameter don’t seem to be defined in any of the headers in DevStudio 2003, DevStudio 2005 Beta 2 or current SDKs.

  2. Ian Thomas says:

    Yeh, the first thing the owner of this code should do is to make it well known to the Windows Installer guys, and to the VS2003 / VS2005 teams, so there’s a Custom Action DLL built for the MSIExec, and/or incorporated into the non-dotNET bootstrapper.

    The REAL WORLD largely doesn’t have ANY version of the CLR installed, or possibly has .NET v1.1; worse, we find that clients have "experimental" installs of this or that beta or CTP, and it’s hell’s business to sort it out and make something written for v1.1 install and work smoothly.

    I don’t know if you recall Microsoft’s systematically orchestrated chaos that was MDAC data connectivity versioning, but I’d hate to think that you guys think that by providing a little bit of C++ code the transitioning problems for developers and users, for systems going from Win32 to some version of .NET, will be a piece of cake.

    Life’s tough out here!!

  3. Andrei Ignat says:

    Great information.

    How about an API in next versions of .NET about those version ( reflection, any other code)? Why MUST all this code be written in C++ and not in C# ? ( or any other .NET language )?

  4. Andy says:

    John – I just did a search for RUNTIME_INFO_UPGRADE_VERSION in my Visual Studio 2005 Beta 2 folder, and I found it in SDKv2.0includemscoree.h. Maybe you should look in the .NET SDK folder instead of the Platform SDK folder?

    Andrei – personally, I find these functions much more useful in the unmanaged C++ case than in the C# case. If you’re in C# already, then the CLR is already loaded and you can’t really do anything about that. From C++, you can make an intelligent decision based on the versions that are installed. I have to do just that, although I’m not sure if I want to rely on the above code though since I’ll need to do the same sort of thing even if .NET v2.0 isn’t installed, which doesn’t have this ability.

    You can get the current version of the CLR that is running from C#, just call System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion(). If Microsoft is going to expose the above functionality from within .NET, it would probably be in the RuntimeEnvironment class, which appears to be a wrapper for the unmanaged API exposed by mscoree.dll.

  5. Anonymous says:

    How about also expanding this code to include displaying information on service packs and hotfixes. The lack of a "CLR status utility" is disappointing and we had to write our own using variations of the above code.

  6. I found an interesting post that lists some sample code to enumerate the installed versions of the .NET…

  7. I previously posted some sample code to detect the version(s) and service pack levels of the .NET Framework