Click here to Skip to main content
Email Password   helpLost your password?

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

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.

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralBeginner of .Net?
amy_slchan
21:34 22 Dec '09  
Dear all,

I am a beginner of .Net, can anyone tell me what tools I need to have and how to start on this.

please help.

SL
QuestionDoes not work on a new installation
oware
13:57 5 Nov '09  
Hello, I used this class to make a loader who checks if framework is installed, if yes, then it executes a .net program, if not it runs the .net framework setup.

I compiled it with VS 2005 and It works fine in some computers, but in a new windows SP 2 installation with no .net framework (nor 1.x neither 2.x) it does not work, the message is "Application can't start because its configuration is incorrect" (or something like that, sorry this message appears in spanish, I'm in mexico), what can I do? I am making a portable application who will execute from a CD-ROM but it needs the .net framework 2 to run, so the loader must check if the framework is installed to run the application. What do you suggest me to do? Should I check the registry? I think I am doing something wrong in the compilation because the program that comes with the source code runs perfectly in the new windows installation.

Thanks a lot

Omar
Generalcan you protect a dll of .NET framework?
JLKEngine008
17:25 28 Mar '09  
can you protect a dll of .NET framework?
GeneralIs there a way I can detect if a particular dll has a dependency on the .NET framework?
samal_recw95
12:22 31 Jul '07  
I have a task at hand to find out if a particular DLL has a dependency on .NET framework. I was serching the net for some info on how to do this and I stumbled on your article. So thought you might have some idea as to how to do this. I know this is slightly off the topic of the article you have posted but, thought you might have some clue on this.
Generalnice work!
Dave Calkins
7:27 2 May '07  
Thanks for this class! Well done Smile

GeneralDoesn't appear to detect DotNet 3.0
markh524
11:41 12 Apr '07  
I tried the app after installing the Microsoft .NET Framework 3.0 Redistributable Package found here:
http://www.microsoft.com/downloads/details.aspx?FamilyId=10CC340B-F857-4A14-83F5-25634C3BF043&displaylang=en

...and it didn't detect it.

It detected the 1.1 and 2.0 CLRs.

I ran the CLR Version Detection Technology Sample found here:
http://msdn2.microsoft.com/en-us/library/ydh6b3yb.aspx

...and it didn't detect it either.

This article entitled, Detecting .NET Framework 3.0 and Earlier Releases, suggests using a registry key:
http://msdn2.microsoft.com/en-us/library/aa480198.aspx#netfx30_topic14

- Mark H
GeneralRe: Doesn't appear to detect DotNet 3.0
Nishant Sivakumar
11:43 12 Apr '07  
Yeah, the article and code needs to be updated to support .NET 3. I wrote it before .NET 3 was out.

Regards,
Nish
Fly on your way like an eagle
Fly as high as the sun
On your wings like an eagle
Fly and touch the sun

GeneralRe: Doesn't appear to detect DotNet 3.0
markh524
12:10 12 Apr '07  
On this page entitled, .NET Framework 3.0 Versioning and Deployment Q&A:
http://msdn2.microsoft.com/en-us/netframework/aa663314.aspx

it says...

Q: Which version of the Common Language Runtime (CLR) does the .NET Framework 3.0 use?
The .NET Framework 3.0 uses the 2.0 version of the CLR. With this release, the overall developer platform version has been decoupled from the core CLR engine version. We expect the lower level components of the .NET Framework such as the engine to change less than higher level APIs, and this decoupling helps retain customers' investments in the technology.

...so your app does return the correct value (for the CLR) but some probably want it to return the version of the framework.

- Mark H
GeneralRe: Doesn't appear to detect DotNet 3.0
Nishant Sivakumar
11:46 12 Apr '07  
Mark,

The behavior is actually correct. .NET 3 does not have a new CLR. It uses the .NET 2 CLR. So there's really no CLR to detect there.

But I think I'll update the app (when I get some time) to indicate the presence of .NET 3 in some other way.

Regards,
Nish
Fly on your way like an eagle
Fly as high as the sun
On your wings like an eagle
Fly and touch the sun

Generalerror compiling with visual studio 2005
hareshet
7:13 8 Feb '07  
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
Darren/London/UK
7:45 2 Mar '07  
I'm struggling with the same problem.
Any help much appreciated, thanks.

AnswerFix
Darren/London/UK
1:52 5 Mar '07  
I just needed to add the standard ATL include for it to build successfully.
#include "atlbase.h"

This was not necessary in VS2003, so I can only assume my other ATL includes must have previously pulled in "atlbase.h" implicitly.

Perhaps the ATL headers changed between VS2003/2005?

GeneralC style strings
Stephen Hewitt
18:32 16 May '06  
My comment is on the following function:
bool GetInstallRootPath(TCHAR* szRootPath, DWORD dwBufferSize);

Given that another method returns results in a std::vector<std::string> I can see no reason why the GetInstallRootPath doesn't return the result path in a std::string. This would save the caller from having to worry about such things a buffer size, if the NULL character should be counted, if the count is bytes or characters and such.


Steve
GeneralUse the registry and static methods
GFeldman
8:24 21 Jul '05  
A better way to test for which .Net versions are installed is to check
HKLM\Microsoft\.NETFramework\policy\v1.1
HKLM\Microsoft\.NETFramework\policy\v2.0

See http://support.microsoft.com/default.aspx?scid=kb;%5BLN%5D;315291 for more info.

I also think it makes more sense just to make these methods static, as there's nothing the client of these can do to change the state via this class. Caching the results in member variables is a case of premature optimization. It's either unnecessary (how often would a user create the object, interrogate it, and then keep it around for subsequent interrogation - when the results are guaranteed not to change?) or error prone (the system state can change behind its back if the user does keep the object around).

Gary


GeneralRe: Use the registry and static methods
code4jigar
0:15 6 Jul '06  
Thanks buddy.. i was lookin for such thing. But is there any way by which i can get info abt .net FW installed and in use without using registry?

GeneralRe: Use the registry and static methods
GFeldman
10:16 6 Jul '06  
I don't think there's any reliable way to do that. Even if someone cloned an existing .Net installation, I don't think it would function without the registry entries.

While I'm not a fan of using the registry to store things, I don't have any objection to interrogating it for information. Is there a reason you don't want to use the registry?

Gary
Generaldebug build always returns true
HudsonKane
4:14 21 Jul '05  
I'm sure I am missing something, but when I run the debug build IsDoNetPreset() always returns true. The release build functions as expected
Thank You
GeneralRe: debug build always returns true
Nishant Sivakumar
4:36 21 Jul '05  
Ouch! It's a horribly silly bug Frown

In the constructor I do this :-

m_bDotNetPresent = IsDotNetPresent();

And IsDotNetPresent is defined as :-

bool CDetectDotNet::IsDotNetPresent()
{
return m_bDotNetPresent;
}

The constructor should actually do this :-

m_bDotNetPresent = IsDotNetPresentInternal();

Do I feel dumb or what! Frown
GeneralRe: debug build always returns true
Nishant Sivakumar
4:38 21 Jul '05  
The bug got introduced when I added caching in 2.1 Frown

*sigh* I'll upload the corrected code once I get home Frown

Meanwhile you can make the change to the constructor Frown
GeneralRe: debug build always returns true
HudsonKane
4:49 21 Jul '05  
doh! can't believe I didn't catch that either.
Thanks, and great little class!
GeneralRe: debug build always returns true
Nishant Sivakumar
7:21 21 Jul '05  
HudsonKane wrote: doh! can't believe I didn't catch that either.
Makes two of us now Smile

HudsonKane wrote: Thanks, and great little class!
Thank you.

BTW I've uploaded the fixed version!
GeneralGreat,Suggestion
kv4000
4:00 21 Jul '05  
one:
Can you solve it using C# code?

two:
Can you explain explicitly how to combine framework with application?

Doing so in order to make this application run on the target machine which

is not installed framework! Thank you ,Good Luck to you!

GeneralRe: Great,Suggestion
Nishant Sivakumar
4:07 21 Jul '05  
Hello kv,

kv4000 wrote: one:
Can you solve it using C# code?

You cannot attempt to detect .NET using a .NET application! The code that detects .NET needs to be independent of .NET to be of any practical use.


kv4000 wrote: two:
Can you explain explicitly how to combine framework with application?

You need to look up your installer's documentation. Most installers that support .NET, have easily configurable options to include the .NET redistributable in the setup. Some of them automatically prompt the user as to whether he wants to go to the .NET download URL.

GeneralRe: Great,Suggestion
Nishant Sivakumar
4:09 21 Jul '05  
You could apply the following method :

[run detector app]
|
if (.NET is detected) -- go ahead with your installation.
|
if (.NET is not present) -- start with .NET installation, followed by your installation.
GeneralCustom Action for Windows Installer
gxdata
23:14 17 Jul '05  
A handy addition would be to allow this detection to be used by the Windows Installer, as a Custom Action.
It would really require a separate article, though - since there's quite a bit to explain about Windows Installer (latest is version 3.1, release 2).

I'm not volunteering to write such an article, by the way! Wink


Last Updated 21 Jul 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010