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

The coding gentleman's guide to detecting the .NET Framework

Rate me:
Please Sign up or sign in to vote.
4.27/5 (38 votes)
21 Jul 20055 min read 217.9K   1.6K   68   36
A C++ class that will detect and enumerate the active CLR versions on a machine

Image 1

Overview

The CDetectDotNet class detects whether the .NET Framework is installed on a machine and also retrieves the list of Framework versions currently installed. Not unpredictably, it's an unmanaged C++ class with zero dependency on anything .NET related - it would have been utterly worthless if it required the .NET framework to run. I believe the class would be pretty useful in installer applications, that might need to detect whether the target machine has .NET installed and if so, whether a specific version is available. One important thing to keep in mind is that the .NET versions retrieved represent CLR versions (along with the matching BCL), and may not necessarily mean that the corresponding SDKs are present; the distinction to be observed here is that between the full SDK and the .NET runtime.

Using the class

Add DetectDotNet.h and DetectDotNet.cpp to your project.

#include "DetectDotNet.h"

// . . .

CDetectDotNet detect;
vector<string> CLRVersions;

cout << "Is .NET present : " 
    << (detect.IsDotNetPresent() ? "Yes" : "No") << endl;
TCHAR szPath[300];
cout << "Root Path : "
    << (detect.GetInstallRootPath(szPath, 299) ? szPath : "") << endl;
cout << "Number of CLRs detected : " 
    << (int)detect.EnumerateCLRVersions(CLRVersions) << endl;    
cout << "CLR versions available :-" << endl;

for(vector<string>::iterator it = CLRVersions.begin(); 
    it < CLRVersions.end(); it++)
{
    cout << *it << endl;
}

cout << "Press any key..." << endl;
getch();

Sample Output

Is .NET present : Yes
Root Path : C:\WINDOWS\Microsoft.NET\Framework\
Number of CLRs detected : 2
CLR versions available :-
2.0.50215
1.1.4322
Press any key...

Public Interface

  • CDetectDotNet();

    Constructs a CDetectDotNet object.

  • bool IsDotNetPresent();

    Detects if .NET is present on a system.

    Returns true if .NET is detected on the system and false otherwise.

  • bool GetInstallRootPath(TCHAR* szRootPath, DWORD dwBufferSize);

    Retrieves the root installation path of the .NET Framework

    • szRootPath - The .NET root installation path is returned in szRootPath.
    • dwBufferSize - Specifies the length of szRootPath.

    Returns true if successful and false otherwise.

  • size_t EnumerateCLRVersions(vector<string>& CLRVersions);

    Enumerates the list of active CLR versions in the system.

    • CLRVersions - This will contain the list of CLR versions detected on the system.

    Returns the count of CLR versions detected.

Implementation

Detecting the .NET Framework

The extremely simple technique I use to detect whether the .NET Framework is present on the system is to LoadLibrary "mscoree.dll" and if that succeeds, I also do a GetProcAddress for "GetCORVersion" which takes care of scenarios where the OS version comes with a placeholder DLL. I also cache the result (in the class constructor), so that subsequent calls need not repeat the LoadLibrary/GetProcAddress calls.

bool CDetectDotNet::IsDotNetPresent()
{
    return m_bDotNetPresent;
}
bool CDetectDotNet::IsDotNetPresentInternal()
{
    bool bRet = false;
    //Attempt to LoadLibrary "mscoree.dll" (the CLR EE shim)
    HMODULE hModule = LoadLibrary(_T("mscoree"));
    if(hModule)
    {    
        //Okay - that worked, but just to ensure that this is
        //not a placeholder DLL shipped with some earlier OS versions,
        //we attempt to GetProcAddress "GetCORVersion".
        bRet = (GetProcAddress(hModule, "GetCORVersion") != NULL);
        FreeLibrary(hModule);
    }
    return bRet;
}

Enumerating CLR versions

Here's the mechanism I use to enumerate the available CLR versions on a system.

  1. Get the root install path for the .NET Framework (obtained from the registry). Typically, this would be something like C:\WINDOWS\Microsoft.NET\Framework.
  2. Find all directories in the root install path. Most (but not all) of these directories would be the base paths of individual .NET Framework versions.
  3. Use GetRequestedRuntimeInfo to query the presence of the CLR versions detected in the previous step. Surprisingly, the pwszVersion parameter that GetRequestedRuntimeInfo takes is actually the directory name within the .NET root path where a specific CLR version exists. Thus, if you rename the default folder name (usually something like "v2.0.50215") to something else, GetRequestedRuntimeInfo will still succeed.
  4. Once GetRequestedRuntimeInfo has succeeded, we still need to get the version string (to take care of situations where someone has renamed the folder name). To do this I simply look for mscorlib.dll within that folder and extract its file version, and you can be sure that any successful implementation of the framework will definitely have an mscorlib.dll in its installation folder.

Note - You'll find GetRequestedRuntimeInfo in mscoree.h. Here's what I have in the version that comes with VS.NET 2005 Beta 2.

STDAPI GetRequestedRuntimeInfo(
    LPCWSTR pExe, LPCWSTR pwszVersion, LPCWSTR pConfigurationFile, 
    DWORD startupFlags, DWORD runtimeInfoFlags, 
    LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength, 
    LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength);

Here's a screenshot of my .NET installation root folder.

Image 2

Notice the folder named "renamed", which is actually the installation folder for "v2.0.50215". The call to GetRequestedRuntimeInfo with

pwszVersion
set to "renamed" actually succeeds (possibly because GetRequestedRuntimeInfo uses a very similar technique to what I do to extract the version). Funnily, the version string returned by the function in pVersion is the name of the folder and not the real version string, but this is not a big bother as we know that there will be an mscorlib.dll in this folder and that its version string will give us the CLR version contained in the folder.

Getting the root installation folder

This is extracted from the registry.

HKEY_LOCAL_MACHINE
    SOFTWARE
        Microsoft
            .NETFramework : InstallRoot (REG_SZ)
bool CDetectDotNet::GetInstallRootPath(TCHAR* szRootPath, DWORD dwBufferSize)
{
    bool bRet = false;
    if(m_szInstallRootPath)
    {
        size_t siz = _tcslen(m_szInstallRootPath);
        if(dwBufferSize > siz)
        {
            _tcsncpy(szRootPath, m_szInstallRootPath, siz);
            szRootPath[siz] = NULL;
            bRet = true;
        }
    }
    return bRet;
}
bool CDetectDotNet::GetInstallRootPathInternal(TCHAR* szRootPath, DWORD dwBufferSize)
{
    bool bRet = false;
    TCHAR szRegPath[] = _T("SOFTWARE\\Microsoft\\.NETFramework");
    HKEY hKey = NULL;
    if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegPath, 0, 
        KEY_READ, &hKey) == ERROR_SUCCESS)
    {    
        DWORD dwSize = dwBufferSize;
        if(RegQueryValueEx(hKey, _T("InstallRoot"), NULL, NULL,
            reinterpret_cast<LPBYTE>(szRootPath), 
            &dwSize) == ERROR_SUCCESS)
        {
            bRet = (dwSize <= dwBufferSize);
        }
        RegCloseKey(hKey);
    }
    return bRet;
}

Getting the .NET version from the mscorlib assembly

string CDetectDotNet::GetVersionFromFolderName(string szFolderName)
{
    string strRet = "<Version could not be extracted from mscorlib>";
    TCHAR szRootPath[g_cPathSize];
    if(GetInstallRootPath(szRootPath, g_cPathSize))
    {
        USES_CONVERSION;
        string szFilepath = T2A(szRootPath);
        szFilepath += (szFolderName + "\\mscorlib.dll");
        string s = GetDotNetVersion(A2CT(szFilepath.c_str()));
        if(s.size() > 0)
            strRet = s;
    }
    return strRet;
}
string CDetectDotNet::GetDotNetVersion(LPCTSTR szFolder)
{
    string strRet = _T("");
    LPVOID m_lpData = NULL;
    TCHAR buff[MAX_PATH + 1] = {0};
    _tcsncpy(buff, szFolder, MAX_PATH);
    DWORD dwHandle = 0;
    DWORD dwVerInfoSize = GetFileVersionInfoSize(buff, &dwHandle);    

    if(dwVerInfoSize != 0) //Success
    {
        m_lpData = malloc(dwVerInfoSize);
        if(GetFileVersionInfo(buff, dwHandle, 
            dwVerInfoSize, m_lpData) == FALSE)
        {
            free(m_lpData);
            m_lpData = NULL;
        }
        else
        {
            UINT cbTranslate = 0;

            struct LANGANDCODEPAGE 
            {
                WORD wLanguage;
                WORD wCodePage;
            } *lpTranslate;    

            if(VerQueryValue(m_lpData,_T("\\VarFileInfo\\Translation"),
                (LPVOID*)&lpTranslate,&cbTranslate))
            {
                int count = (int)(cbTranslate/sizeof(struct LANGANDCODEPAGE));

                for(int i=0; i < count; i++ )
                {
                    TCHAR SubBlock[128];
                    HRESULT hr = StringCchPrintf(SubBlock, 
                        127,_T("\\StringFileInfo\\%04x%04x\\%s"),
                        lpTranslate[i].wLanguage,
                        lpTranslate[i].wCodePage,_T("FileVersion"));    

                    if(SUCCEEDED(hr))
                    {
                        UINT dwBytes = 0;
                        TCHAR* lpBuffer;

                        if(VerQueryValue(m_lpData, SubBlock, 
                            (LPVOID*)&lpBuffer, &dwBytes))
                        {    
                            USES_CONVERSION;
                            strRet = T2A(lpBuffer);
                            for(unsigned int x = 0, j = 0; j < strRet.size(); j++)
                            {
                                if(strRet[j] == '.')
                                {
                                    if(++x == 3)
                                    {
                                        strRet.erase(j,strRet.size() - j);
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                    }        
                }
            }            
        }
    }
    return strRet;
}

Check if a specific .NET version exists

bool CDetectDotNet::CheckForSpecificCLRVersionInternal(LPCWSTR pszVersion)
{
    bool bRet = false;
    if( m_bDotNetPresent )
    {
        UINT prevErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
        HMODULE hModule = LoadLibrary(_T("mscoree"));
        if(hModule)
        {    
            FPGetRequestedRuntimeInfo pGetRequestedRuntimeInfo =
                reinterpret_cast<FPGetRequestedRuntimeInfo>(
                GetProcAddress(hModule, "GetRequestedRuntimeInfo"));
            if(pGetRequestedRuntimeInfo)
            {
                LPWSTR dirBuff = NULL;
                DWORD dwDir = 0;
                LPWSTR verBuff = NULL;
                DWORD dwVer = 0;

                pGetRequestedRuntimeInfo(NULL, pszVersion,
                    NULL,0,0,
                    dirBuff, dwDir, &dwDir,
                    verBuff, dwVer, &dwVer);

                dirBuff = new WCHAR[dwDir + 1];
                verBuff = new WCHAR[dwVer + 1];

                HRESULT hr = pGetRequestedRuntimeInfo(NULL, pszVersion,
                    NULL,0,0,dirBuff, dwDir, &dwDir,verBuff, dwVer, &dwVer);

                bRet = (hr == S_OK);

                delete[] verBuff;
                delete[] dirBuff;
            }
            FreeLibrary(hModule);            
        }
        SetErrorMode(prevErrMode);
    }
    return bRet;
}

Get list of CLR versions

size_t CDetectDotNet::EnumerateCLRVersions(vector<string>& CLRVersions)
{
    CLRVersions.clear();
    USES_CONVERSION;
    vector<string> PossibleCLRVersions;
    EnumeratePossibleCLRVersionsInternal(PossibleCLRVersions);
    for(vector<string>::iterator it = PossibleCLRVersions.begin(); 
        it < PossibleCLRVersions.end(); it++)
    {                
        if(CheckForSpecificCLRVersionInternal(A2CW((*it).c_str())))
        {            
            CLRVersions.push_back(GetVersionFromFolderName(*it));
        }        
    }
    return CLRVersions.size();
}
size_t CDetectDotNet::EnumeratePossibleCLRVersionsInternal(
    vector<string>& PossibleCLRVersions)
{
    PossibleCLRVersions.clear();
    if(m_bDotNetPresent)
    {
        TCHAR szRootBuff[g_cPathSize];
        if(GetInstallRootPath(szRootBuff, g_cPathSize))
        {
            WIN32_FIND_DATA finddata = {0};
            _tcsncat(szRootBuff, _T("*"), 1);
            HANDLE hFind = FindFirstFile(szRootBuff, &finddata);
            if(hFind != INVALID_HANDLE_VALUE)
            {
                do
                {
                    if( finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
                    {
                        PossibleCLRVersions.push_back(finddata.cFileName);
                    }
                }while(FindNextFile(hFind, &finddata));
                FindClose(hFind);
            }
        }
    }
    return PossibleCLRVersions.size();
}

Final notes

I haven't tested this code out on every Windows or .NET version out there, so I cannot guarantee that it will work correctly all the time. But if it does not work for you, please let me know of it through the forum, and if you post an accurate problem description, I'll make efforts to implement a fix or a work-around. But you need to tell me, because if I don't know that a bug exists, I am not going to be able to fix it. Enjoy!

History

  • July 21st 2005 - v2.2
    • Fixed a bug in the constructor that had crept in during the 2.1 update. (I was incorrectly calling IsDotNetPresent to initialize m_bDotNetPresent when I should have been calling IsDotNetPresentInternal)
  • July 17th 2005 - v2.1
    • Caches IsDotNetPresent and GetInstallRootPath internally (so the actual detection code is executed only once - in the constructor)
    • Modified the version enumeration code so that the class can detect CLR versions even if their directory names have been modified. It does this by extracting the version string out of the mscorlib.dll for that specific CLR version.
  • July 16th 2005 - v2.0
    • Removed the dependencies on mscoree.h and mscoree.lib.
    • Removed the need to invoke an external detector stub executable.
    • Uses GetRequestedRuntimeInfo to check for a specific CLR version.
    • Updated IsDotNetPresent to verify that the mscoree.dll that LoadLibrary successfully loaded is not a placeholder DLL shipped in earlier OS versions.
  • July 15th 2005 - v1.0
    • First version of the class uploaded.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
QuestionBeginner of .Net? Pin
amy_slchan22-Dec-09 20:34
amy_slchan22-Dec-09 20:34 
QuestionDoes not work on a new installation Pin
oware5-Nov-09 12:57
oware5-Nov-09 12:57 
Questioncan you protect a dll of .NET framework? Pin
JLKEngine00828-Mar-09 16:25
JLKEngine00828-Mar-09 16:25 
QuestionIs there a way I can detect if a particular dll has a dependency on the .NET framework? Pin
samal_recw9531-Jul-07 11:22
samal_recw9531-Jul-07 11:22 
Generalnice work! Pin
Dave Calkins2-May-07 6:27
Dave Calkins2-May-07 6:27 
GeneralDoesn't appear to detect DotNet 3.0 Pin
markh52412-Apr-07 10:41
markh52412-Apr-07 10:41 
GeneralRe: Doesn't appear to detect DotNet 3.0 Pin
Nish Nishant12-Apr-07 10:43
sitebuilderNish Nishant12-Apr-07 10:43 
GeneralRe: Doesn't appear to detect DotNet 3.0 Pin
markh52412-Apr-07 11:10
markh52412-Apr-07 11:10 
GeneralRe: Doesn't appear to detect DotNet 3.0 Pin
Nish Nishant12-Apr-07 10:46
sitebuilderNish Nishant12-Apr-07 10:46 
Generalerror compiling with visual studio 2005 Pin
hareshet8-Feb-07 6:13
hareshet8-Feb-07 6:13 
hi I really need a program like this, so I've tried to compile it in my VS2005 but I have a linkage error -

error LNK2001: unresolved external symbol "unsigned int (__stdcall* ATL::g_pfnGetThreadACP)(void)" (?g_pfnGetThreadACP@ATL@@3P6GIXZA)

I cannot find the source of this function
also I tried to make a new project and apply this class to it but it is not working good - many conversion problems.

can anyone help me?
thanks


itai shmida
GeneralRe: error compiling with visual studio 2005 Pin
Darren/London/UK2-Mar-07 6:45
Darren/London/UK2-Mar-07 6:45 
AnswerFix Pin
Darren/London/UK5-Mar-07 0:52
Darren/London/UK5-Mar-07 0:52 
GeneralC style strings Pin
Stephen Hewitt16-May-06 17:32
Stephen Hewitt16-May-06 17:32 
GeneralUse the registry and static methods Pin
GFeldman21-Jul-05 7:24
GFeldman21-Jul-05 7:24 
GeneralRe: Use the registry and static methods Pin
code4jigar5-Jul-06 23:15
code4jigar5-Jul-06 23:15 
GeneralRe: Use the registry and static methods Pin
GFeldman6-Jul-06 9:16
GFeldman6-Jul-06 9:16 
Generaldebug build always returns true Pin
HudsonKane21-Jul-05 3:14
HudsonKane21-Jul-05 3:14 
GeneralRe: debug build always returns true Pin
Nish Nishant21-Jul-05 3:36
sitebuilderNish Nishant21-Jul-05 3:36 
GeneralRe: debug build always returns true Pin
Nish Nishant21-Jul-05 3:38
sitebuilderNish Nishant21-Jul-05 3:38 
GeneralRe: debug build always returns true Pin
HudsonKane21-Jul-05 3:49
HudsonKane21-Jul-05 3:49 
GeneralRe: debug build always returns true Pin
Nish Nishant21-Jul-05 6:21
sitebuilderNish Nishant21-Jul-05 6:21 
GeneralGreat,Suggestion Pin
Alenty21-Jul-05 3:00
Alenty21-Jul-05 3:00 
GeneralRe: Great,Suggestion Pin
Nish Nishant21-Jul-05 3:07
sitebuilderNish Nishant21-Jul-05 3:07 
GeneralRe: Great,Suggestion Pin
Nish Nishant21-Jul-05 3:09
sitebuilderNish Nishant21-Jul-05 3:09 
GeneralCustom Action for Windows Installer Pin
gxdata17-Jul-05 22:14
gxdata17-Jul-05 22:14 

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.