5,545,925 members and growing! (16,802 online)
Email Password   helpLost your password?
Database » Database » SQL Server     Intermediate

Locate SQL Server instances on the local network

By James Curran

An article on locating MS SQL Server instances on the local network, and retrieving information about them.
C#, SQL, Windows, .NET 1.1, .NETSQL Server, Visual Studio, SQL 2000, VS.NET2003, DBA, Dev

Posted: 24 Nov 2005
Updated: 24 Nov 2005
Views: 96,076
Bookmarked: 85 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
36 votes for this Article.
Popularity: 6.80 Rating: 4.37 out of 5
3 votes, 8.3%
1
1 vote, 2.8%
2
3 votes, 8.3%
3
4 votes, 11.1%
4
25 votes, 69.4%
5

Introduction

This articles shows how to retrieve a list of PCs on the local network which are running MS SQL Server, and gets information about the instances, such as server name, instance name, version, and databases.

Background

To get a list of the nearby SQL Servers is as simple as typing "osql /L" on a command prompt. But getting them programmatically, for use inside an application, is a bit trickier. The most common use for this would be to create a connection string building form in managed code, which will be the subject of my next article.

Note also, that while the subject matter is SQL Server, the first half of this article deals mostly with sockets.

To create a list of SQL Server instances, I first tried several different methods, all of which had two things in common: they took a very long time, and they were only marginally successful at finding SQL Servers instances on the network. Eventually, I had the idea of running "osql -L" with a packet sniffer running (I used Ethereal), to see how osql was doing it.

Its method was to send out a Broadcast UDP packet on port 1434, with the contents of just a byte of 0x02, to which each server would respond. Sending such a message is quite simple:

Socket socket = new Socket(AddressFamily.InterNetwork, 
              SocketType.Dgram, ProtocolType.Udp );
//  For .Net v 1.1 the options are cumbersome & hidden.

    socket.SetSocketOption(SocketOptionLevel.Socket, 
              SocketOptionName.Broadcast, 1);
    socket.SetSocketOption(SocketOptionLevel.Socket, 
              SocketOptionName.ReceiveTimeout, 3000);
    
//  For .Net v 2.0 it's a bit simpler

//  socket.EnableBroadcast = true;    // for .Net v2.0

//  socket.ReceiveTimeout = 3000;     // for .Net v2.0


IPEndPoint ep = new IPEndPoint(IPAddress.Broadcast, 1434);
byte[] msg = new byte[] { 0x02 };
socket.SendTo(msg, ep);

It's vitally important that you set the timeout. Without it, the Receive method will just keep waiting (forever!) until it gets something. However, since we'll be waiting for responses from an unknown number of servers, we have to decide to give up at some point. I've set that point, initially, at 3 seconds. Before I realized that the Broadcast & Timeout options were available (but hidden) on the Socket class, I had this elaborate class which derived from UdpClient and implemented asynchronous receiving.

As it turns out, with the proper options set, getting a response is as simple as you'd hope.

byte[] bytBuffer = new byte[256];
socket.Receive(bytBuffer);

The Receive method will get the response from one server, so we'll have to put that code in a loop to get all of them. Of course, once we've gotten a response, we need to interpret it. For this, I've created a simple class called SqlServerInfo, which I'll explain further in a minute.

After we've received, processed and stored the response, we just keep looping until we don't get a response within the timeout. The downside of using a timeout, is that when it is reached, the Socket throws a SocketException, which means we have to incur the overhead time to process a throw every time we call this method. This is in addition to the timeout delay itself. I was able to find a way to minimize this.

I initially set the timeout to 3 seconds. The problem here was that after each response, I'd call Receive again, and the timeout timer would start over. So the total time for the method would be: the total actual processing time + the timeout + the throw overhead. This was about 5 seconds in my tests. This seems way too long to me -- remember, this is something we are going to want to do in the Page_Load of a form. One thing I noticed was that after I send the broadcast message, there would be a short delay as the message went out, the remote servers received it and prepared their responses. However, after that delay, all the responses would come in a pack. So, I decided, after I processed the first response, to reset the timeout to 0.3 seconds. This cut the total time down to about 1-2 seconds, a much more reasonable delay. (However, if there were no servers at all on the network, it would still take 3+ seconds to realize that.)

Now, back to interpreting the response we've gotten, which is quite simple. The first three bytes are a byte of 0x05, followed by two bytes giving the length of the rest which is straight ASCII text of pairs of items, separated by semicolons:

data item 1 name ; data item 1 value ; data item 2 name  ; data item 2 value;

or more specifically:

ServerName;DATA001;InstanceName;MSSQLSERVER;IsClustered;No;Version;8.00.194;tcp;1433

To make use of this, we'll have to convert from a byte array of ASCII characters to a .NET string of UNICODE characters, which the framework is nice enough to provide a method for (although it does bury in under System.Text.ASCIIEncoding.ASCII). Then, separate the parts, and move the the data values to properties where they can be accessed easier.

Of course, while the information provided in the broadcast response is useful, it doesn't provide one vitally important piece of data: the list of databases on that server. To get that, we need to use more traditional database access methods.

But, to do that, we are going to have to connect to the server, which means a connection string, which means a username & password. SqlServerInfo provides read/write properties for those as well as a boolean IntegratedSecurity option (the other properties are read-only).

Once we've established a connection, getting the list of catalogs is a method built right into the OleDbConnection object:

DataTable schemaTable = 
   myConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Catalogs, null);

Using the code

Everything is packaged in the class SqlServerInfo. It has one static method, Seek(), which gets the information about the SQL Server instances on the network, and returns an array of SqlServerInfo objects.

SqlServerInfo[] servers = SqlServerInfo.Seek();

Each SqlServerInfo object contains a number of properties describing that particular MS SQL Server instance. Each of these is a read-only property.

  • public string ServerName
  • public string InstanceName
  • public bool IsClustered
  • public string Version
  • public int TcpPort
  • public string NamedPipe
  • public IPAddress Address

InstanceName is typically "MSSQLSERVER", which is the default if it isn't given a specific name at installation. Version should be "8.0.xxx" for SQL Server 2000. And TcpPort will typically be 1433.

Note that, although Address is a property, in the current implementation it will always be null --- until I can figure a way of finding out what the server's IP address is. The Socket.Receive method doesn't say what machine is sending the data -- it is just assumed to be the machine you just transmitted to -- the naiveté of which is apparent when one sends a broadcast message. (If anyone knows how to get that information out of a Socket, please let me know.)

Next there are four Read/Write properties, which must be set to get any more information out of this class:

  • public string UserId
  • public string Password
  • public bool IntegratedSecurity
  • public int TimeOut

These establish how we are going to attempt to connect to that server. IntegratedSecurity defaults to true, and TimeOut defaults to 2 seconds, so if these work for you, you don't have to do anything more. Setting either UserId or Password sets IntegratedSecurity to false.

The final property retrieves a list of databases available on the server.

  • public StringCollection Catalogs

The first time it is called, it will attempt to connect to the server, so the above property needs to be set right.

Finally, there are two methods:

  • public bool TestConnection()
  • public override string ToString()

TestConnection is just as its name says --- it tests if you can connect with the given userID/password.

ToString returns either the ServerName (if the InstanceName is the default) or ServerName / InstanceName. Either way it is what you need to specify as the Data Source in a connection string. As a ToString, you can set the DataSource property of a ListBox or similar control to an array of SqlServerInfo objects, and the appropriate values will be displayed.

SqlServerInfo Data = servers[0];
Console.WriteLine(Data.ToString());
Console.WriteLine("Version:", Data.Version);

Data.IntegratedSecurity = true;
foreach(string Catalog in Data.Catalogs)
{
    Console.WriteLine("    {0}", Catalog);
}

History

  • 20-Nov 2005 - v 1.0 Initial release.

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

About the Author

James Curran


20+ years as a developer : Assembly, C, C++ and C# (in that order) with sidelines in ASP/VBScript, ASP.Net, JavaScript, Perl, QuickBasic, VisualBasic, plus a few others which I'm not going to mention because if I did someone might ask me to use them again (shutter)

Microsoft MVP in VC++ (1994-2004)

I also run www.NJTheater.com as a hobby.

Full resume & stuff at NovelTheory.com

Underused blog at HonestIllusion.com

Occupation: Web Developer
Location: United States United States

Other popular Database articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 25 of 45 (Total in Forum: 45) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralIP Address fixmemberSalvador S. Delvisco Jr.11:34 19 Sep '08  
GeneralGreat work James I made slight modifications for my purposes that i think might be useful for some.memberMember 10538206:35 19 Aug '08  
GeneralUse SMO to get SQL ServersmemberMember 476816323:13 14 May '08  
AnswerRe: Use SMO to get SQL ServersmemberMember 10538206:50 19 Aug '08  
GeneralGot an Exception while comming in the loop second timemembernsadhasivam4:05 5 May '08  
GeneralAlways times out, no servers respondmemberghawkes10:25 23 Mar '08  
GeneralRe: Always times out, no servers respondmembernsadhasivam4:46 5 May '08  
GeneralDoesn't return if the network is disabledmemberarupk_das_200218:28 12 Nov '07  
GeneralGet list of all the terminalsmemberMathur Preeti20:21 9 Feb '07  
QuestionHow about WMImembersmch23:15 25 Jan '07  
AnswerRe: How about WMImemberJames Curran3:19 26 Jan '07  
Generaldetecting named msde instancesmemberrajeshatsrin21:52 22 Aug '06  
GeneralTCP disabled?membermcm_xyz14:51 22 Aug '06  
GeneralUsing Registry [modified]memberBahadir Cambel4:12 7 Jun '06  
GeneralULTIMATE! Many thanks!memberXTonos0:33 30 May '06  
GeneralTimeout problemmemberKnut Morten Ovesen22:20 19 Feb '06  
GeneralRe: Timeout problemmemberKnut Morten Ovesen1:51 24 Feb '06  
GeneralRe: Timeout problemmemberScott S.3:30 2 Mar '06  
GeneralA little enhancementmembercarlopagliei4:53 10 Feb '06  
GeneralRe: A little enhancementmemberrajeshatsrin3:35 23 Aug '06  
GeneralWith VMWare it does not workmemberIhor Bobak10:32 9 Jan '06  
GeneralHow do you enumerate servers that the SQL Browser service is disabled?membermheschl12:05 5 Jan '06  
GeneralI get 3 instances of the same server instead of onememberGeorge Marselis4:21 4 Dec '05  
GeneralOracle & MySQLmemberGeorge Marselis12:59 3 Dec '05  
General