Tuesday, September 14, 2004
« Let's be Intelligent with our Intellisen... | Main | Simple Blogger »

Here's a code snippet that I wrote the other day.  I had a need to discover the various SQL Server instances on the network.  I couldn't reliably use SQL DMO for a variety of reasons; for one, there might not be any client tools installed locally.  Therefore, I decided to dive down into ODBC and use its built-in functionality that I am pretty confident will be on the users' machines.

There are a few items I'd like to state before we get to the code.

  • This code will not work (that is, it will return null) if you don't currently have an active network connection
  • I'm working on getting this to work regardless of connectivity - so stay tuned.
  • Similar functionality is expected in VS 2005, but this gets the job done.

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

namespace Devstone.Tools.DataAccess.Discovery {

   public sealed class DbEnumerator {
      [DllImport("odbc32.dll")]
      private static extern short SQLAllocHandle(short hType, IntPtr inputHandle, out IntPtr outputHandlePtr);
      [DllImport("odbc32.dll")]
      private static extern short SQLSetEnvAttr(IntPtr hEnv, int attribute, IntPtr valuePtr, int stringLength);
      [DllImport("odbc32.dll")]
      private static extern short SQLFreeHandle(short hType, IntPtr handle);
      [DllImport("odbc32.dll",CharSet=CharSet.Ansi)]
      private static extern short SQLBrowseConnect(IntPtr hconn, string inString, short inStringLength, StringBuilder outString, short outStringLength, out short outLengthNeeded);

      private const short SQL_HANDLE_ENV = 1;
      private const short SQL_HANDLE_DBC = 2;
      private const int SQL_ATTR_ODBC_VERSION = 200;
      private const int SQL_OV_ODBC3 = 3;
      private const short SQL_SUCCESS = 0;
      private const short SQL_SUCCESS_WITH_INFO = 1;
      private const short SQL_NEED_DATA = 99;

      private DbEnumerator() { }

      public static string[] GetSqlServers() {
        
string[] ret = null;
        
IntPtr hEnv = IntPtr.Zero;
        
IntPtr hConn = IntPtr.Zero;
        
const string CONNECTION_STRING = "DRIVER=SQL SERVER";
        
const short DEFAULT_RESULT_SIZE = 1024;
        
StringBuilder outString = new StringBuilder(DEFAULT_RESULT_SIZE);
        
int retCode;

         try {
           
// allocate the environment handle
           
retCode = SQLAllocHandle(SQL_HANDLE_ENV, hEnv, out hEnv);
           
if ( SQL_SUCCESS != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            // set the proper version environment attribute
           
retCode = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (IntPtr)SQL_OV_ODBC3, 0);
           
if ( SQL_SUCCESS != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            // allocate the connection handle
           
retCode = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, out hConn);
           
if ( SQL_SUCCESS != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            short lenNeeded = (short)outString.Capacity;
           
retCode = SQLBrowseConnect(hConn, CONNECTION_STRING, (short)CONNECTION_STRING.Length, outString, DEFAULT_RESULT_SIZE, out lenNeeded);
           
if ( SQL_NEED_DATA == retCode && lenNeeded > DEFAULT_RESULT_SIZE ) {
              
// try again with the new length only if more data is needed and the string needs to be longer
              
// otherwise we'll just end up with the same result again
              
retCode = SQLBrowseConnect(hConn, CONNECTION_STRING, (short)CONNECTION_STRING.Length, outString, (short)(lenNeeded + 1), out lenNeeded);
           
}

            if ( SQL_SUCCESS != retCode && SQL_NEED_DATA != retCode && SQL_SUCCESS_WITH_INFO != retCode ) return null;

            // extract the servers out of the returned string and split them on the commas into the return value
           
Regex servers = new Regex(@"^SERVER:Server={(?'SERVERS'.*)};", RegexOptions.Singleline);
           
Match m = servers.Match(outString.ToString());

            if ( m.Success )
              
ret = m.Groups["SERVERS"].Value.Split(new char[] {','});
        
}
        
catch ( Exception ) { /* swallow the exception */ }
         finally {
           
if ( IntPtr.Zero != hConn ) SQLFreeHandle(SQL_HANDLE_DBC, hConn);
           
if ( IntPtr.Zero != hEnv ) SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
        
}

        
return ret;
      }

   } // DbEnumerator class

} // Devstone.Tools.DataAccess namespace

Tuesday, September 14, 2004 9:59:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback