diff --git a/doc/samples/SqlDataSourceEnumeratorExample.cs b/doc/samples/SqlDataSourceEnumeratorExample.cs
new file mode 100644
index 0000000000..279881c672
--- /dev/null
+++ b/doc/samples/SqlDataSourceEnumeratorExample.cs
@@ -0,0 +1,33 @@
+using System;
+//
+using Microsoft.Data.Sql;
+
+class Program
+{
+ static void Main()
+ {
+ // Retrieve the enumerator instance and then the data.
+ SqlDataSourceEnumerator instance =
+ SqlDataSourceEnumerator.Instance;
+ System.Data.DataTable table = instance.GetDataSources();
+
+ // Display the contents of the table.
+ DisplayData(table);
+
+ Console.WriteLine("Press any key to continue.");
+ Console.ReadKey();
+ }
+
+ private static void DisplayData(System.Data.DataTable table)
+ {
+ foreach (System.Data.DataRow row in table.Rows)
+ {
+ foreach (System.Data.DataColumn col in table.Columns)
+ {
+ Console.WriteLine("{0} = {1}", col.ColumnName, row[col]);
+ }
+ Console.WriteLine("============================");
+ }
+ }
+}
+//
diff --git a/doc/samples/SqlDataSourceEnumeratorVersionExample.cs b/doc/samples/SqlDataSourceEnumeratorVersionExample.cs
new file mode 100644
index 0000000000..73eefc1235
--- /dev/null
+++ b/doc/samples/SqlDataSourceEnumeratorVersionExample.cs
@@ -0,0 +1,25 @@
+using System;
+//
+using Microsoft.Data.Sql;
+
+class Program
+{
+ static void Main()
+ {
+ // Retrieve the enumerator instance, and
+ // then retrieve the data sources.
+ SqlDataSourceEnumerator instance =
+ SqlDataSourceEnumerator.Instance;
+ System.Data.DataTable table = instance.GetDataSources();
+
+ // Filter the sources to just show SQL Server 2012 instances.
+ System.Data.DataRow[] rows = table.Select("Version LIKE '11%'");
+ foreach (System.Data.DataRow row in rows)
+ {
+ Console.WriteLine(row["ServerName"]);
+ }
+ Console.WriteLine("Press any key to continue.");
+ Console.ReadKey();
+ }
+}
+//
diff --git a/doc/snippets/Microsoft.Data.Sql/SqlDataSourceEnumerator.xml b/doc/snippets/Microsoft.Data.Sql/SqlDataSourceEnumerator.xml
new file mode 100644
index 0000000000..55e1f2c057
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.Sql/SqlDataSourceEnumerator.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ Provides a mechanism for enumerating all available instances of SQL Server within the local network.
+
+ class exposes this information to the application developer, providing a containing information about all the available servers. This returned table contains a list of server instances that matches the list provided when a user attempts to create a new connection, and on the `Connection Properties` dialog box, expands the drop-down list containing all the available servers.
+
+ ]]>
+
+ Enumerating Instances of SQL Server
+
+
+ Retrieves a containing information about all visible SQL Server instances.
+ A containing information about the visible SQL Server instances.
+
+
10.0.xx for SQL Server 2008
10.50.x for SQL Server 2008 R2
11.0.xx for SQL Server 2012
12.0.xx for SQL Server 2014
13.0.xx for SQL Server 2016
14.0.xx for SQL Server 2017|
+
+> [!NOTE]
+> Due to the nature of the mechanism used by to locate data sources on a network, the method will not always return a complete list of the available servers, and the list might not be the same on every call. If you plan to use this function to let users select a server from a list, make sure that you always also supply an option to type in a name that is not in the list, in case the server enumeration does not return all the available servers. In addition, this method may take a significant amount of time to execute, so be careful about calling it when performance is critical.
+
+## Examples
+ The following console application retrieves information about all the visible SQL Server instances and displays the information in the console window.
+
+[!code-csharp[SqlDataSourceEnumerator.Example#1](~/../sqlclient/doc/samples/SqlDataSourceEnumeratorExample.cs#1)]
+
+ ]]>
+
+ Enumerating Instances of SQL Server
+
+
+ Gets an instance of the , which can be used to retrieve information about available SQL Server instances.
+ An instance of the used to retrieve information about available SQL Server instances.
+
+ class does not provide a constructor. Use the property to retrieve an instance of the class instead.
+
+[!code-csharp[SqlDataSourceEnumeratorExample#1](~/../sqlclient/doc/samples/SqlDataSourceEnumeratorExample.cs#1)]
+
+## Examples
+The following console application displays a list of all the available SQL Server 2005 instances within the local network. This code uses the method to filter the rows in the table returned by the method.
+ [!code-csharp[SqlDataSourceEnumeratorVersionExample#1](~/../sqlclient/doc/samples/SqlDataSourceEnumeratorVersionExample.cs#1)]
+
+ ]]>
+
+ Enumerating Instances of SQL Server
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index 3d63a3f9db..12e8094ff0 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -29,6 +29,15 @@ public SqlNotificationRequest(string userData, string options, int timeout) { }
///
public string UserData { get { throw null; } set { } }
}
+
+ ///
+ public sealed class SqlDataSourceEnumerator : System.Data.Common.DbDataSourceEnumerator
+ {
+ ///
+ public static SqlDataSourceEnumerator Instance { get; }
+ ///
+ public override System.Data.DataTable GetDataSources() { throw null; }
+ }
}
namespace Microsoft.Data.SqlTypes
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Common.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Common.cs
index ed33b6897a..0b09ea3341 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Common.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Common.cs
@@ -3,11 +3,15 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Data.SqlClient.SNI;
+using System;
+using System.Runtime.InteropServices;
namespace Microsoft.Data.SqlClient
{
internal static partial class SNINativeMethodWrapper
{
+ private const string SNI = "Microsoft.Data.SqlClient.SNI.dll";
+
internal enum SniSpecialErrors : uint
{
LocalDBErrorCode = SNICommon.LocalDBErrorCode,
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
index 7f1b3e17ea..4e84fdc406 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
@@ -12,8 +12,6 @@ namespace Microsoft.Data.SqlClient
{
internal static partial class SNINativeMethodWrapper
{
- private const string SNI = "Microsoft.Data.SqlClient.SNI.dll";
-
private static int s_sniMaxComposedSpnLength = -1;
private const int SniOpenTimeOut = -1; // infinite
@@ -200,6 +198,7 @@ internal struct SNI_Error
#endregion
#region DLL Imports
+
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")]
internal static extern uint SNIAddProvider(SNIHandle pConn, ProviderEnum ProvNum, [In] ref uint pInfo);
@@ -306,7 +305,19 @@ private static extern unsafe uint SNISecGenClientContextWrapper(
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
private static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket);
- #endregion
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")]
+ internal static extern IntPtr SNIServerEnumOpen();
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")]
+ internal static extern void SNIServerEnumClose([In] IntPtr packet);
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper", CharSet = CharSet.Unicode)]
+ internal static extern int SNIServerEnumRead([In] IntPtr packet,
+ [In][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer,
+ [In] int bufferLength,
+ [MarshalAs(UnmanagedType.Bool)] out bool more);
+ #endregion
internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId)
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index e1483a26a8..ec72b3a856 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -45,6 +45,15 @@
Microsoft\Data\Common\ActivityCorrelator.cs
+
+
+ Microsoft\Data\Sql\SqlDataSourceEnumerator.cs
+
+
+ Microsoft\Data\Sql\SqlDataSourceEnumeratorManagedHelper.cs
+
+
+ Microsoft\Data\Sql\SqlDataSourceEnumeratorUtil.cs
Microsoft\Data\Common\DbConnectionStringCommon.cs
@@ -633,6 +642,12 @@
+
+ Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs
+
+
+ Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs
+
@@ -861,6 +876,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Unix.cs
new file mode 100644
index 0000000000..6ee3fe3329
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Unix.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System.Data;
+using System.Data.Common;
+using Microsoft.Data.SqlClient.Server;
+
+namespace Microsoft.Data.Sql
+{
+ ///
+ public sealed partial class SqlDataSourceEnumerator : DbDataSourceEnumerator
+ {
+ private partial DataTable GetDataSourcesInternal() => SqlDataSourceEnumeratorManagedHelper.GetDataSources();
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs
index 0147b29f17..d182a8d31f 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs
@@ -11,10 +11,16 @@
namespace Microsoft.Data.SqlClient.SNI
{
- internal class SSRP
+ internal sealed class SSRP
{
private const char SemicolonSeparator = ';';
- private const int SqlServerBrowserPort = 1434;
+ private const int SqlServerBrowserPort = 1434; //port SQL Server Browser
+ private const int RecieveMAXTimeoutsForCLNT_BCAST_EX = 15000; //Default max time for response wait
+ private const int RecieveTimeoutsForCLNT_BCAST_EX = 1000; //subsequent wait time for response after intial wait
+ private const int ServerResponseHeaderSizeForCLNT_BCAST_EX = 3;//(SVR_RESP + RESP_SIZE) https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/2e1560c9-5097-4023-9f5e-72b9ff1ec3b1
+ private const int ValidResponseSizeForCLNT_BCAST_EX = 4096; //valid reponse size should be less than 4096
+ private const int FirstTimeoutForCLNT_BCAST_EX = 5000;//wait for first response for 5 seconds
+ private const int CLNT_BCAST_EX = 2;//request packet
///
/// Finds instance port number for given instance name.
@@ -149,8 +155,7 @@ private static byte[] SendUDPRequest(string browserHostname, int port, byte[] re
const int sendTimeOutMs = 1000;
const int receiveTimeOutMs = 1000;
- IPAddress address = null;
- bool isIpAddress = IPAddress.TryParse(browserHostname, out address);
+ bool isIpAddress = IPAddress.TryParse(browserHostname, out IPAddress address);
byte[] responsePacket = null;
using (UdpClient client = new UdpClient(!isIpAddress ? AddressFamily.InterNetwork : address.AddressFamily))
@@ -165,9 +170,52 @@ private static byte[] SendUDPRequest(string browserHostname, int port, byte[] re
responsePacket = receiveTask.Result.Buffer;
}
}
-
return responsePacket;
}
}
+
+ ///
+ /// Sends request to server, and recieves response from server (SQLBrowser) on port 1434 by UDP
+ /// Request (https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/a3035afa-c268-4699-b8fd-4f351e5c8e9e)
+ /// Response (https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/2e1560c9-5097-4023-9f5e-72b9ff1ec3b1)
+ ///
+ /// string constaning list of SVR_RESP(just RESP_DATA)
+ internal static string SendBroadcastUDPRequest()
+ {
+ StringBuilder response = new StringBuilder();
+ byte[] CLNT_BCAST_EX_Request = new byte[1] { CLNT_BCAST_EX }; //0x02
+ // Waits 5 seconds for the first response and every 1 second up to 15 seconds
+ // https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/f2640a2d-3beb-464b-a443-f635842ebc3e#Appendix_A_3
+ int currentTimeOut = FirstTimeoutForCLNT_BCAST_EX;
+
+ using (TrySNIEventScope.Create(nameof(SSRP)))
+ {
+ using (UdpClient clientListener = new UdpClient())
+ {
+ Task sendTask = clientListener.SendAsync(CLNT_BCAST_EX_Request, CLNT_BCAST_EX_Request.Length, new IPEndPoint(IPAddress.Broadcast, SqlServerBrowserPort));
+ Task receiveTask = null;
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Waiting for UDP Client to fetch list of instances.");
+ Stopwatch sw = new Stopwatch(); //for waiting until 15 sec elapsed
+ sw.Start();
+ try
+ {
+ while ((receiveTask = clientListener.ReceiveAsync()).Wait(currentTimeOut) && sw.ElapsedMilliseconds <= RecieveMAXTimeoutsForCLNT_BCAST_EX && receiveTask != null)
+ {
+ currentTimeOut = RecieveTimeoutsForCLNT_BCAST_EX;
+ SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Received instnace info from UDP Client.");
+ if (receiveTask.Result.Buffer.Length < ValidResponseSizeForCLNT_BCAST_EX) //discard invalid response
+ {
+ response.Append(Encoding.UTF7.GetString(receiveTask.Result.Buffer, ServerResponseHeaderSizeForCLNT_BCAST_EX, receiveTask.Result.Buffer.Length - ServerResponseHeaderSizeForCLNT_BCAST_EX)); //RESP_DATA(VARIABLE) - 3 (RESP_SIZE + SVR_RESP)
+ }
+ }
+ }
+ finally
+ {
+ sw.Stop();
+ }
+ }
+ }
+ return response.ToString();
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
index e17a65be95..6f67764f9a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
@@ -180,5 +180,6 @@ internal static int GetRemainingTimeout(int timeout, long start)
return checked((int)remaining);
}
}
+ internal static long GetTimeoutSeconds(int timeout) => GetTimeout((long)timeout * 1000L);
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index 6a6ae668e7..d5ac6b0611 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -34,6 +34,15 @@ public SqlNotificationRequest(string userData, string options, int timeout) { }
///
public string UserData { get { throw null; } set { } }
}
+
+ ///
+ public sealed class SqlDataSourceEnumerator : System.Data.Common.DbDataSourceEnumerator
+ {
+ ///
+ public static SqlDataSourceEnumerator Instance {get;}
+ ///
+ public override System.Data.DataTable GetDataSources(){ throw null; }
+ }
}
namespace Microsoft.Data.SqlClient
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index a4ecd51c67..fe4e7b13da 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -119,6 +119,18 @@
Microsoft\Data\Sql\SqlNotificationRequest.cs
+
+ Microsoft\Data\Sql\SqlDataSourceEnumerator.cs
+
+
+ Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs
+
+
+ Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs
+
+
+ Microsoft\Data\Sql\SqlDataSourceEnumeratorUtil.cs
+
Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
index abbfda7ede..13e35363a8 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
@@ -134,5 +134,17 @@ internal static extern unsafe uint SNISecGenClientContextWrapper(
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr SNIClientCertificateFallbackWrapper(IntPtr pCallbackContext);
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")]
+ internal static extern IntPtr SNIServerEnumOpen();
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")]
+ internal static extern void SNIServerEnumClose([In] IntPtr packet);
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper", CharSet = CharSet.Unicode)]
+ internal static extern int SNIServerEnumRead([In] IntPtr packet,
+ [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer,
+ [In] int bufferLength,
+ [MarshalAs(UnmanagedType.Bool)] out bool more);
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
index b700e4b108..5517ba8c0e 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
@@ -134,5 +134,17 @@ internal static extern unsafe uint SNISecGenClientContextWrapper(
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr SNIClientCertificateFallbackWrapper(IntPtr pCallbackContext);
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")]
+ internal static extern IntPtr SNIServerEnumOpen();
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")]
+ internal static extern void SNIServerEnumClose([In] IntPtr packet);
+
+ [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper", CharSet = CharSet.Unicode)]
+ internal static extern int SNIServerEnumRead([In] IntPtr packet,
+ [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer,
+ [In] int bufferLength,
+ [MarshalAs(UnmanagedType.Bool)] out bool more);
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
index d1fb0ad3e5..39ba5c5259 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
@@ -760,6 +760,26 @@ internal static uint SNIInitialize()
return SNIInitialize(LocalAppContextSwitches.UseSystemDefaultSecureProtocols, IntPtr.Zero);
}
+ internal static IntPtr SNIServerEnumOpen() => s_is64bitProcess ?
+ SNINativeManagedWrapperX64.SNIServerEnumOpen() :
+ SNINativeManagedWrapperX86.SNIServerEnumOpen();
+
+ internal static int SNIServerEnumRead([In] IntPtr packet, [In, Out] char[] readbuffer, int bufferLength, out bool more) => s_is64bitProcess ?
+ SNINativeManagedWrapperX64.SNIServerEnumRead(packet, readbuffer, bufferLength, out more) :
+ SNINativeManagedWrapperX86.SNIServerEnumRead(packet, readbuffer, bufferLength, out more);
+
+ internal static void SNIServerEnumClose([In] IntPtr packet)
+ {
+ if (s_is64bitProcess)
+ {
+ SNINativeManagedWrapperX64.SNIServerEnumClose(packet);
+ }
+ else
+ {
+ SNINativeManagedWrapperX86.SNIServerEnumClose(packet);
+ }
+ }
+
internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo)
{
// initialize consumer info for MARS
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
index 025ea7d020..7be7f61bb4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
@@ -221,6 +221,8 @@ internal static long GetTimeout(long timeoutMilliseconds)
return result;
}
+ internal static long GetTimeoutSeconds(int timeout) => GetTimeout((long)timeout * 1000L);
+
internal static bool TimeoutHasExpired(long timeoutTime)
{
bool result = false;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index 7d86c2d7ff..8db89b51b7 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -1227,6 +1227,8 @@ static internal Exception InvalidMixedUsageOfCredentialAndAccessToken()
=> InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfCredentialAndAccessToken));
#endregion
+ internal static bool IsEmpty(string str) => string.IsNullOrEmpty(str);
+ internal static readonly IntPtr s_ptrZero = IntPtr.Zero;
#if NETFRAMEWORK
#region netfx project only
internal static Task CreatedTaskWithException(Exception ex)
@@ -1381,7 +1383,6 @@ internal static InvalidOperationException ComputerNameEx(int lastError)
internal const float FailoverTimeoutStepForTnir = 0.125F; // Fraction of timeout to use in case of Transparent Network IP resolution.
internal const int MinimumTimeoutForTnirMs = 500; // The first login attempt in Transparent network IP Resolution
- internal static readonly IntPtr s_ptrZero = IntPtr.Zero; // IntPtr.Zero
internal static readonly int s_ptrSize = IntPtr.Size;
internal static readonly IntPtr s_invalidPtr = new(-1); // use for INVALID_HANDLE
@@ -1472,7 +1473,6 @@ internal static IntPtr IntPtrOffset(IntPtr pbase, int offset)
return (IntPtr)checked(pbase.ToInt64() + offset);
}
- internal static bool IsEmpty(string str) => string.IsNullOrEmpty(str);
#endregion
#else
#region netcore project only
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Windows.cs
new file mode 100644
index 0000000000..83ce5085e7
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Windows.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System.Data;
+using System.Data.Common;
+using Microsoft.Data.SqlClient.Server;
+
+namespace Microsoft.Data.Sql
+{
+ ///
+ public sealed partial class SqlDataSourceEnumerator : DbDataSourceEnumerator
+ {
+ private partial DataTable GetDataSourcesInternal()
+ {
+#if NETFRAMEWORK
+ return SqlDataSourceEnumeratorNativeHelper.GetDataSources();
+#else
+ return SqlClient.TdsParserStateObjectFactory.UseManagedSNI ? SqlDataSourceEnumeratorManagedHelper.GetDataSources() : SqlDataSourceEnumeratorNativeHelper.GetDataSources();
+#endif
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs
new file mode 100644
index 0000000000..e8f7aac29c
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.Data;
+using System.Data.Common;
+
+namespace Microsoft.Data.Sql
+{
+ ///
+ public sealed partial class SqlDataSourceEnumerator : DbDataSourceEnumerator
+ {
+ private static readonly Lazy s_singletonInstance = new(() => new SqlDataSourceEnumerator());
+
+ private SqlDataSourceEnumerator() : base(){}
+
+ ///
+ public static SqlDataSourceEnumerator Instance => s_singletonInstance.Value;
+
+ ///
+ override public DataTable GetDataSources() => GetDataSourcesInternal();
+
+ private partial DataTable GetDataSourcesInternal();
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.cs
new file mode 100644
index 0000000000..43be666e0d
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
+using System.Data;
+using Microsoft.Data.Sql;
+
+namespace Microsoft.Data.SqlClient.Server
+{
+ ///
+ /// Provides a mechanism for enumerating all available instances of SQL Server within the local network
+ ///
+ internal static class SqlDataSourceEnumeratorManagedHelper
+ {
+ ///
+ /// Provides a mechanism for enumerating all available instances of SQL Server within the local network.
+ ///
+ /// DataTable with ServerName,InstanceName,IsClustered and Version
+ internal static DataTable GetDataSources()
+ {
+ // TODO: Implement multicast request besides the implemented broadcast request.
+ throw new System.NotImplementedException(StringsHelper.net_MethodNotImplementedException);
+ }
+
+ private static DataTable ParseServerEnumString(string serverInstances)
+ {
+ DataTable dataTable = SqlDataSourceEnumeratorUtil.PrepareDataTable();
+ DataRow dataRow;
+
+ if (serverInstances.Length == 0)
+ {
+ return dataTable;
+ }
+
+ string[] numOfServerInstances = serverInstances.Split(SqlDataSourceEnumeratorUtil.s_endOfServerInstanceDelimiter_Managed, System.StringSplitOptions.None);
+ SqlClientEventSource.Log.TryTraceEvent(" Number of recieved server instances are {2}",
+ nameof(SqlDataSourceEnumeratorManagedHelper), nameof(ParseServerEnumString), numOfServerInstances.Length);
+
+ foreach (string currentServerInstance in numOfServerInstances)
+ {
+ Dictionary InstanceDetails = new();
+ string[] delimitedKeyValues = currentServerInstance.Split(SqlDataSourceEnumeratorUtil.InstanceKeysDelimiter);
+ string currentKey = string.Empty;
+
+ for (int keyvalue = 0; keyvalue < delimitedKeyValues.Length; keyvalue++)
+ {
+ if (keyvalue % 2 == 0)
+ {
+ currentKey = delimitedKeyValues[keyvalue];
+ }
+ else if (currentKey != string.Empty)
+ {
+ InstanceDetails.Add(currentKey, delimitedKeyValues[keyvalue]);
+ }
+ }
+
+ if (InstanceDetails.Count > 0)
+ {
+ dataRow = dataTable.NewRow();
+ dataRow[0] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.ServerNameCol) == true ?
+ InstanceDetails[SqlDataSourceEnumeratorUtil.ServerNameCol] : string.Empty;
+ dataRow[1] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.InstanceNameCol) == true ?
+ InstanceDetails[SqlDataSourceEnumeratorUtil.InstanceNameCol] : string.Empty;
+ dataRow[2] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.IsClusteredCol) == true ?
+ InstanceDetails[SqlDataSourceEnumeratorUtil.IsClusteredCol] : string.Empty;
+ dataRow[3] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.VersionNameCol) == true ?
+ InstanceDetails[SqlDataSourceEnumeratorUtil.VersionNameCol] : string.Empty;
+
+ dataTable.Rows.Add(dataRow);
+ }
+ }
+ return dataTable.SetColumnsReadOnly();
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs
new file mode 100644
index 0000000000..db40a6439e
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs
@@ -0,0 +1,179 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Data;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Security;
+using System.Text;
+using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient;
+using static Microsoft.Data.Sql.SqlDataSourceEnumeratorUtil;
+
+namespace Microsoft.Data.Sql
+{
+ ///
+ /// Provides a mechanism for enumerating all available instances of SQL Server within the local network
+ ///
+ internal static class SqlDataSourceEnumeratorNativeHelper
+ {
+ ///
+ /// Retrieves a DataTable containing information about all visible SQL Server instances
+ ///
+ ///
+ internal static DataTable GetDataSources()
+ {
+ (new NamedPermissionSet("FullTrust")).Demand(); // SQLBUDT 244304
+ char[] buffer = null;
+ StringBuilder strbldr = new();
+
+ int bufferSize = 1024;
+ int readLength = 0;
+ buffer = new char[bufferSize];
+ bool more = true;
+ bool failure = false;
+ IntPtr handle = ADP.s_ptrZero;
+
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ long s_timeoutTime = TdsParserStaticMethods.GetTimeoutSeconds(ADP.DefaultCommandTimeout);
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ { }
+ finally
+ {
+ handle = SNINativeMethodWrapper.SNIServerEnumOpen();
+ SqlClientEventSource.Log.TryTraceEvent(" {3} returned handle = {4}.",
+ nameof(SqlDataSourceEnumeratorNativeHelper),
+ nameof(GetDataSources),
+ nameof(SNINativeMethodWrapper.SNIServerEnumOpen), handle);
+ }
+
+ if (handle != ADP.s_ptrZero)
+ {
+ while (more && !TdsParserStaticMethods.TimeoutHasExpired(s_timeoutTime))
+ {
+ readLength = SNINativeMethodWrapper.SNIServerEnumRead(handle, buffer, bufferSize, out more);
+
+ SqlClientEventSource.Log.TryTraceEvent(" {2} returned 'readlength':{3}, and 'more':{4} with 'bufferSize' of {5}",
+ nameof(SqlDataSourceEnumeratorNativeHelper),
+ nameof(GetDataSources),
+ nameof(SNINativeMethodWrapper.SNIServerEnumRead),
+ readLength, more, bufferSize);
+ if (readLength > bufferSize)
+ {
+ failure = true;
+ more = false;
+ }
+ else if (readLength > 0)
+ {
+ strbldr.Append(buffer, 0, readLength);
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (handle != ADP.s_ptrZero)
+ {
+ SNINativeMethodWrapper.SNIServerEnumClose(handle);
+ SqlClientEventSource.Log.TryTraceEvent(" {3} called.",
+ nameof(SqlDataSourceEnumeratorNativeHelper),
+ nameof(GetDataSources),
+ nameof(SNINativeMethodWrapper.SNIServerEnumClose));
+ }
+ }
+
+ if (failure)
+ {
+ Debug.Assert(false, $"{nameof(GetDataSources)}:{nameof(SNINativeMethodWrapper.SNIServerEnumRead)} returned bad length");
+ SqlClientEventSource.Log.TryTraceEvent(" {2} returned bad length, requested buffer {3}, received {4}",
+ nameof(SqlDataSourceEnumeratorNativeHelper),
+ nameof(GetDataSources),
+ nameof(SNINativeMethodWrapper.SNIServerEnumRead),
+ bufferSize, readLength);
+
+ throw ADP.ArgumentOutOfRange(StringsHelper.GetString(Strings.ADP_ParameterValueOutOfRange, readLength), nameof(readLength));
+ }
+ return ParseServerEnumString(strbldr.ToString());
+ }
+
+ private static DataTable ParseServerEnumString(string serverInstances)
+ {
+ DataTable dataTable = PrepareDataTable();
+ string serverName = null;
+ string instanceName = null;
+ string isClustered = null;
+ string version = null;
+ string[] serverinstanceslist = serverInstances.Split(EndOfServerInstanceDelimiter_Native);
+ SqlClientEventSource.Log.TryTraceEvent(" Number of recieved server instances are {2}",
+ nameof(SqlDataSourceEnumeratorNativeHelper), nameof(ParseServerEnumString), serverinstanceslist.Length);
+
+ // Every row comes in the format "serverName\instanceName;Clustered:[Yes|No];Version:.."
+ // Every row is terminated by a null character.
+ // Process one row at a time
+ foreach (string instance in serverinstanceslist)
+ {
+ string value = instance.Trim(EndOfServerInstanceDelimiter_Native); // MDAC 91934
+ if (value.Length == 0)
+ {
+ continue;
+ }
+ foreach (string instance2 in value.Split(InstanceKeysDelimiter))
+ {
+ if (serverName == null)
+ {
+ foreach (string instance3 in instance2.Split(ServerNamesAndInstanceDelimiter))
+ {
+ if (serverName == null)
+ {
+ serverName = instance3;
+ continue;
+ }
+ Debug.Assert(instanceName == null, $"{nameof(instanceName)}({instanceName}) is not null.");
+ instanceName = instance3;
+ }
+ continue;
+ }
+ if (isClustered == null)
+ {
+ Debug.Assert(string.Compare(Clustered, 0, instance2, 0, s_clusteredLength, StringComparison.OrdinalIgnoreCase) == 0,
+ $"{nameof(Clustered)} ({Clustered}) doesn't equal {nameof(instance2)} ({instance2})");
+ isClustered = instance2.Substring(s_clusteredLength);
+ continue;
+ }
+ Debug.Assert(version == null, $"{nameof(version)}({version}) is not null.");
+ Debug.Assert(string.Compare(SqlDataSourceEnumeratorUtil.Version, 0, instance2, 0, s_versionLength, StringComparison.OrdinalIgnoreCase) == 0,
+ $"{nameof(SqlDataSourceEnumeratorUtil.Version)} ({SqlDataSourceEnumeratorUtil.Version}) doesn't equal {nameof(instance2)} ({instance2})");
+ version = instance2.Substring(s_versionLength);
+ }
+
+ string query = "ServerName='" + serverName + "'";
+
+ if (!ADP.IsEmpty(instanceName))
+ { // SQL BU DT 20006584: only append instanceName if present.
+ query += " AND InstanceName='" + instanceName + "'";
+ }
+
+ // SNI returns dupes - do not add them. SQL BU DT 290323
+ if (dataTable.Select(query).Length == 0)
+ {
+ DataRow dataRow = dataTable.NewRow();
+ dataRow[0] = serverName;
+ dataRow[1] = instanceName;
+ dataRow[2] = isClustered;
+ dataRow[3] = version;
+ dataTable.Rows.Add(dataRow);
+ }
+ serverName = null;
+ instanceName = null;
+ isClustered = null;
+ version = null;
+ }
+ return dataTable.SetColumnsReadOnly();
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorUtil.cs
new file mode 100644
index 0000000000..fb6972d8cf
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorUtil.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Data;
+using System.Globalization;
+
+namespace Microsoft.Data.Sql
+{
+ ///
+ /// const values for SqlDataSourceEnumerator
+ ///
+ internal static class SqlDataSourceEnumeratorUtil
+ {
+ internal const string ServerNameCol = "ServerName";
+ internal const string InstanceNameCol = "InstanceName";
+ internal const string IsClusteredCol = "IsClustered";
+ internal const string VersionNameCol = "Version";
+
+ internal const string Version = "Version:";
+ internal const string Clustered = "Clustered:";
+ internal static readonly int s_versionLength = Version.Length;
+ internal static readonly int s_clusteredLength = Clustered.Length;
+
+ internal static readonly string[] s_endOfServerInstanceDelimiter_Managed = new[] { ";;" };
+ internal const char EndOfServerInstanceDelimiter_Native = '\0';
+ internal const char InstanceKeysDelimiter = ';';
+ internal const char ServerNamesAndInstanceDelimiter = '\\';
+
+ internal static DataTable PrepareDataTable()
+ {
+ DataTable dataTable = new("SqlDataSources");
+ dataTable.Locale = CultureInfo.InvariantCulture;
+ dataTable.Columns.Add(ServerNameCol, typeof(string));
+ dataTable.Columns.Add(InstanceNameCol, typeof(string));
+ dataTable.Columns.Add(IsClusteredCol, typeof(string));
+ dataTable.Columns.Add(VersionNameCol, typeof(string));
+
+ return dataTable;
+ }
+
+ ///
+ /// Sets all columns read-only.
+ ///
+ internal static DataTable SetColumnsReadOnly(this DataTable dataTable)
+ {
+ foreach (DataColumn column in dataTable.Columns)
+ {
+ column.ReadOnly = true;
+ }
+ return dataTable;
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
index ab9ad736bf..44ff1f1ce7 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
@@ -29,7 +29,7 @@
TCECryptoNativeBaselineRsa.txt
-
+
@@ -159,6 +159,9 @@
+
+
+
@@ -239,7 +242,7 @@
-
+
@@ -319,6 +322,7 @@
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs
new file mode 100644
index 0000000000..118a2412c3
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.ServiceProcess;
+using Microsoft.Data.Sql;
+using Xunit;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+#if NET50_OR_LATER
+ [System.Runtime.Versioning.SupportedOSPlatform("windows")]
+#endif
+ public class SqlDataSourceEnumeratorTest
+ {
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsNotUsingManagedSNIOnWindows))]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void SqlDataSourceEnumerator_NativeSNI()
+ {
+ // The returned rows depends on the running services which could be zero or more.
+ int count = GetDSEnumerator().GetDataSources().Rows.Count;
+ Assert.InRange(count, 0, 65536);
+ }
+
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsUsingManagedSNI))]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void SqlDataSourceEnumerator_ManagedSNI()
+ {
+ // After adding the managed SNI support, this test should have the same result as SqlDataSourceEnumerator_NativeSNI
+ Assert.Throws(() => GetDSEnumerator().GetDataSources());
+ }
+
+ private SqlDataSourceEnumerator GetDSEnumerator()
+ {
+ // SQL Server Browser runs as a Windows service.
+ // TODO: This assessment can be done on CI.
+ ServiceController sc = new("SQLBrowser");
+ Assert.Equal(ServiceControllerStatus.Running, sc.Status);
+
+ return SqlDataSourceEnumerator.Instance;
+ }
+ }
+}
diff --git a/tools/props/Versions.props b/tools/props/Versions.props
index eb18b2115d..ac93368fa7 100644
--- a/tools/props/Versions.props
+++ b/tools/props/Versions.props
@@ -78,6 +78,7 @@
161.41011.9
10.50.1600.1
0.12.1
+ 6.0.0
$(NugetPackageVersion)