Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Which machines in my LAN are running X DBMS

3.00/5 (3 votes)
10 Jun 2009CPOL4 min read 1   246  
Use NetServerEnum to find servers running particular services.

Introduction

How can I get a list of the servers in my LAN that are running Microsoft SQL Server or Oracle?

I've seen this question come up from time to time here at CP, as well as the microsoft.public.vc.mfc newsgroup. Short of actually going around to each of those servers and looking for signs of either DBMS, what other ways exist for finding out such information?

Finding Microsoft SQL Server

As it turns out, Microsoft has provided a nifty little function called NetServerEnum() that can handle this task quite easily. The seventh argument to this function indicates what type of servers we are interested in enumerating. By specifying SV_TYPE_SQLSERVER, we can get a list of machines that are running Microsoft SQL Server. We can use this function like:

C++
LPSERVER_INFO_101 pBuffer = NULL;
DWORD             dwEntriesRead,
                  dwTotalEntries;
NET_API_STATUS    nStatus;
    
nStatus = NetServerEnum(NULL, 
                        101, 
                        (LPBYTE *) &pBuffer, // memory is allocated by NetServerEnum()
                        MAX_PREFERRED_LENGTH, 
                        &dwEntriesRead, 
                        &dwTotalEntries, 
                        SV_TYPE_SQLSERVER,
                        NULL,
                        0);

if (ERROR_SUCCESS == nStatus)
    ...

Since we specified MAX_PREFERRED_LENGTH as the fourth argument, dwEntriesRead and dwTotalEntries should have the same value. At this point, we simply need to loop through each of the SERVER_INFO_101 structures pointed to by pBuffer:

C++
LPSERVER_INFO_101 pServerInfo = pBuffer;

while (dwEntriesRead--)
{
    ...
    pServerInfo++;
}

So what's so special about this? Nothing, until you consider that there are other DBMSs that may need to be sought. For example, Oracle and MySQL are two other DBMSs that run on the Windows platform. So, until Microsoft acquires those, there's no way for NetServerEnum() to know about them. At least not directly...

Finding Oracle

What if we could enumerate each machine in the LAN and "ask" if it's running a particular DBMS? The only thing we'd need to do is use SV_TYPE_ALL as the fourth argument to NetServerEnum(). For each machine found, we call a few service-related functions to see if any of a number of services are present and running. This would look something like:

C++
SC_HANDLE hSCM = OpenSCManager((LPCTSTR) pServerInfo->sv101_name, 
                                NULL, SC_MANAGER_CONNECT);

if (hSCM != NULL)
{
    SC_HANDLE hService = OpenService(hSCM, _T("name_of_service"), 
                                     SERVICE_QUERY_STATUS);
    if (hService != NULL)
    {
        SERVICE_STATUS ss;
        if (QueryServiceStatus(hService, &ss) != FALSE)
            ...

        CloseServiceHandle(hService);
    }

    CloseServiceHandle(hSCM);
}

You may be asking, "Where does the list of service names come from?" In short, I do not not know for sure, beyond what I was able to find on the Internet. We do have one machine in our LAN that is running Microsoft SQL Server, but that's it. I was able to come up with a few service names for Oracle, and one for MySQL, but those may not be the "main" service. Check the source code for this list.

If you have several service names that you are interested in, just put them in some sort of list/array, and wrap the OpenService() call with a loop that iterates each of those service names.

At this point, you know what services are running on what machines. The call to QueryServiceStatus() will return one of seven statuses (e.g., running, paused). If you are only interested in those services that are actually running, look for the SERVICE_RUNNING status. If you don't care what the status is, just whether it is present or not, the call to OpenService() will suffice.

Putting it all together

Now that we've found everything, what to do with them? While it's certainly not required, for tidying up purposes, I created a couple of very small classes to hold the information. The first class, CMachineInfo, holds the name of a machine, and an array of its services. This looks like:

C++
class CMachineInfo : public CObject
{
public:
    CMachineInfo() {}
    CMachineInfo( LPCTSTR lpszName )
        : m_strName(lpszName)
    {
    }

    virtual ~CMachineInfo()
    {
        for (int x = 0; x < m_arrServices.GetSize(); x++)
            delete (CServiceInfo *) m_arrServices.GetAt(x);
    }
    
    int AddService( CServiceInfo *pServiceInfo )
        { return m_arrServices.Add(pServiceInfo); }

    CString GetName( void ) const { return m_strName; }
    void GetServices( CServiceInfoArray &arr ) const
    {
        arr.Copy(m_arrServices);
    }

#ifdef _DEBUG
    virtual void AssertValid() const
    {
        CObject::AssertValid();

        ASSERT(! m_strName.IsEmpty());
    }

    virtual void Dump(CDumpContext& dc) const
    {
        CObject::Dump(dc);

        dc << _T("Machine name = ") << m_strName << "\n";

        for (int x = 0; x < m_arrServices.GetSize(); x++)
            m_arrServices.GetAt(x)->Dump(dc);
    }
#endif

private:
    CString m_strName;
    CServiceInfoArray m_arrServices;
};

typedef CArray<CMachineInfo*, CMachineInfo*> CMachineInfoArray;

The CMachineInfoArray type is an array that holds all of the machines. While trying to do the right thing with this class by making the two member variables private, and providing getter and setter methods, the GetServices() method caused me some initial grief. I wanted something like:

C++
const CServiceInfoArray& GetServices( void ) const
{
    return m_arrServices;
}

This was all well and good, but it meant that the caller could remove the const qualifier, and add (invalid) items to the array. Not exactly what I wanted! With a little nudging from Michael Dunn, I settled on the method that requires the caller to supply their own CArray.

The other class, CServiceInfo, is used to hold the name of a service, and its status. This looks like:

C++
class CServiceInfo : public CObject
{
public:
    CServiceInfo() {}
    CServiceInfo( LPCTSTR lpszName, LPCTSTR lpszStatus )
        : m_strName(lpszName), m_strStatus(lpszStatus)
    {
    }

    virtual ~CServiceInfo() {}

    CString GetName( void ) const { return m_strName; }
    CString GetStatus( void ) const { return m_strStatus; }

#ifdef _DEBUG
    virtual void AssertValid() const
    {
        CObject::AssertValid();

        ASSERT(! m_strName.IsEmpty());
    }
    
    virtual void Dump(CDumpContext& dc) const
    {
        CObject::Dump(dc);

        dc << _T("Service name = ") << m_strName << "\n";
        dc << _T("Status = ") << m_strStatus << "\n";
    }
#endif
    
private:
    CString m_strName;
    CString m_strStatus;
};

typedef CArray<CServiceInfo*, CServiceInfo*> CServiceInfoArray;

Both of these classes were derived from CObject simply so that I could make use of the AssertValid() and Dump() methods.

To make use of the classes, just create an instance of CMachineInfoArray (e.g., m_arrMachineInfo), and populate accordingly. This looks like:

C++
LPSERVER_INFO_101 pServerInfo = NULL,
                  pBuffer = NULL;
DWORD             dwEntriesRead,
                  dwTotalEntries;
NET_API_STATUS    nStatus;
    
nStatus = NetServerEnum(NULL, 
                        101, 
                        (LPBYTE *) &pBuffer, 
                        MAX_PREFERRED_LENGTH, 
                        &dwEntriesRead, 
                        &dwTotalEntries, 
                        SV_TYPE_ALL,
                        NULL,
                        0);

if (ERROR_SUCCESS == nStatus)
{
    pServerInfo = pBuffer;

    while (dwEntriesRead--)
    {
        CMachineInfo *pMachineInfo = 
          new CMachineInfo((LPCTSTR) pServerInfo->sv101_name);
        int nIndex = m_arrMachineInfo.Add(pMachineInfo);

        SC_HANDLE hSCM = OpenSCManager((LPCTSTR) pServerInfo->sv101_name, 
                                        NULL, SC_MANAGER_CONNECT);
        if (hSCM != NULL)
        {
            for (int x = 0; x < m_arrServiceNames.GetSize(); x++)
            {
                SC_HANDLE hService = 
                  OpenService(hSCM, m_arrServiceNames[x], SERVICE_QUERY_STATUS);
                if (hService != NULL)
                {
                    CString strStatus(_T("Status unknown."));
                    SERVICE_STATUS ss;

                    if (QueryServiceStatus(hService, &ss) != FALSE)
                        m_mapStatus.Lookup(ss.dwCurrentState, strStatus);

                    CServiceInfo *pServiceInfo = 
                      new CServiceInfo(m_arrServiceNames[x], strStatus);
                    pMachineInfo->AddService(pServiceInfo);

                    CloseServiceHandle(hService);
                }
            }

            CloseServiceHandle(hSCM);
        }
       
        // remove the machine from the list if it had no services
        CServiceInfoArray arr;
        pMachineInfo->GetServices(arr);
        if (arr.GetSize() == 0)
        {
            delete (CMachineInfo *) m_arrMachineInfo.GetAt(nIndex);
            m_arrMachineInfo.RemoveAt(nIndex);
        }

        pServerInfo++;
    }

    NetApiBufferFree(pBuffer);
}

#ifdef _DEBUG
for (int x = 0; x < m_arrMachineInfo.GetSize(); x++)
    m_arrMachineInfo.GetAt(x)->Dump(afxDump);
#endif

What about IIS?

Someone recently asked about finding machines that are running IIS. It should be no big deal to add the names of those services to the list. They are:

  • Iisadmin - IIS Admin Service
  • Msftpsvc - FTP Publishing Service
  • Nntpsvc - Microsoft NNTP Service
  • Smtpsvc - Microsoft SMTP Service
  • W3svc - World Wide Web Publishing Service

Conclusion

There's nothing especially difficult about this, and it could be made easier or harder, depending on your requirements. A few things to keep in mind, however.

First, if the remote machine does not have the RPC service running, the call to OpenSCManager() will fail with error 1722. If you are querying a machine that does have the RPC service running yet the response time from the above code is still slow or appears blocked, drop me a note with details.

Second, you might find it better to use a secondary thread for this. This is what I did while putting the demo together, so that the UI remained responsive. I don't know for sure, but OpenSCManager() appeared to block its thread when it was trying to communicate with a machine that did not have the RPC service running. A secondary thread made it much more tolerable as I was then able to put progress indicators on the UI.

Enjoy!

License

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