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