Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Import Project="..\Directory.Packages.props" />
<ItemGroup>
<!-- Reference latest stable version of MDS and AKV provider -->
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.2" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageVersion Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="6.1.2" />
</ItemGroup>
</Project>
203 changes: 203 additions & 0 deletions doc/apps/AzureAuthentication/App.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
using System.Diagnostics;

using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;

namespace Microsoft.Data.SqlClient.Samples.AzureAuthentication;

/// <summary>
/// Console application that validates SqlClient connectivity using Entra ID (formerly Azure Active
/// Directory) authentication.
/// </summary>
public class App : IDisposable
{
// ──────────────────────────────────────────────────────────────────
#region Construction / Disposal

/// <inheritdoc />
public void Dispose()
{
_eventListener?.Dispose();
}

#endregion

// ──────────────────────────────────────────────────────────────────
#region Internal Methods

/// <summary>
/// Options for <see cref="Run"/>.
/// </summary>
internal class RunOptions
{
/// <summary>
/// The ADO.NET connection string to use.
/// </summary>
public string ConnectionString { get; set; } = string.Empty;

/// <summary>
/// When <see langword="true"/>, SqlClient events are emitted to the console.
/// </summary>
public bool LogEvents { get; set; } = false;

/// <summary>
/// When <see langword="true"/>, execution pauses to allow dotnet-trace attachment.
/// </summary>
public bool Trace { get; set; } = false;

/// <summary>
/// When <see langword="true"/>, detailed error information is displayed.
/// </summary>
public bool Verbose { get; set; } = false;
}

/// <summary>
/// Runs the connectivity test against SQL Server using the specified options.
/// </summary>
/// <param name="options">The options controlling the connectivity test.</param>
/// <returns>0 on success; non-zero on failure.</returns>
internal int Run(RunOptions options)
{
Out($"""
{AppName}
---------------------------

Packages used:
SqlClient: {PackageVersions.MicrosoftDataSqlClient}
AKV Provider: {PackageVersions.MicrosoftDataSqlClientAlwaysEncryptedAzureKeyVaultProvider}
Azure: {PackageVersions.AzureExtensionsVersion}

""");

try
{
// Canonicalize the connection string for emission.
SqlConnectionStringBuilder builder = new(options.ConnectionString);

Out($"""
Connection details:
Data Source: {builder.DataSource}
Initial Catalog: {builder.InitialCatalog}
Authentication: {builder.Authentication}

""");

if (options.Verbose)
{
Out($"""
Full connection string:
{builder}

""");
}
}
catch (Exception ex)
{
Err($"""
Failed to parse connection string:
{ex.Message}
""");

if (options.Verbose)
{
Err($" {ex}");
}
return 1;
}

// Enable SqlClient event logging if requested.
if (options.LogEvents)
{
string prefix = "[EVENT]";
Out($"SqlClient event logging enabled; events will be prefixed with {prefix}");

_eventListener = new SqlClientEventListener(Out, prefix);
}

// Pause for trace attachment if requested.
if (options.Trace)
{
Out($"""
Execution paused; attach dotnet-trace and press Enter to resume:

dotnet-trace collect -p {Process.GetCurrentProcess().Id} --providers Microsoft.Data.SqlClient.EventSource:1FFF:5

""");
Console.ReadLine();
}

// Touch the AKV Provider type to ensure its assembly is present and loadable.
_ = typeof(SqlColumnEncryptionAzureKeyVaultProvider);

try
{
Out("Testing connectivity...");

using SqlConnection connection = new(options.ConnectionString);
connection.Open();

Console.ForegroundColor = ConsoleColor.Green;
Out("Connected successfully!");
Console.ResetColor();
Out($" Server version: {connection.ServerVersion}");

return 0;
}
catch (Exception ex)
{
Err($"""
Connection failed:
{ex.Message}
""");

if (options.Verbose)
{
Err($" {ex}");
}

return 1;
}
}

#endregion

// ──────────────────────────────────────────────────────────────────
#region Private Helpers

/// <summary>
/// Writes an informational message to standard output.
/// </summary>
/// <param name="message">The message to write.</param>
internal static void Out(string message)
{
Console.Out.WriteLine(message);
}

/// <summary>
/// Writes an error message to standard error in red.
/// </summary>
/// <param name="message">The message to write.</param>
internal static void Err(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine(message);
Console.ResetColor();
}

#endregion

// ──────────────────────────────────────────────────────────────────
#region Private Fields

/// <summary>
/// The display name of the application.
/// </summary>
internal const string AppName = "Azure Authentication Tester";

/// <summary>
/// The optional event listener used to capture SqlClient diagnostic events.
/// </summary>
private SqlClientEventListener? _eventListener;

#endregion
}
45 changes: 45 additions & 0 deletions doc/apps/AzureAuthentication/AzureAuthentication.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net481;net10.0</TargetFrameworks>
<RootNamespace>Microsoft.Data.SqlClient.Samples.AzureAuthentication</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<!-- SqlClient Packages -->
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" />
<!--
We include the AKV Provider to ensure there are no transitive dependency conflicts. It is
instantiated to ensure its assembly isn't elided, but we don't actually use it.
-->
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" />

<!-- Include the Azure package if a version for it was specified. -->
<PackageReference
Include="Microsoft.Data.SqlClient.Extensions.Azure"
Condition="'$(AzureVersion)' != ''" />
</ItemGroup>

<!--
Define AZURE_EXTENSIONS so that C# code can use #if AZURE_EXTENSIONS to guard references to
types from the optional Extensions.Azure package. Without this, the build would fail when the
package is absent because the compiler would still try to resolve those type references. See
PackageVersions.Partial.cs for usage.
-->
<PropertyGroup Condition="'$(AzureVersion)' != ''">
<DefineConstants>$(DefineConstants);AZURE_EXTENSIONS</DefineConstants>
</PropertyGroup>

<!-- Other Packages -->
<ItemGroup>
<PackageReference Include="System.CommandLine" />
</ItemGroup>

<!-- Generate code with the SqlClient package versions. -->
<Import Project="GeneratePackageVersions.targets" />

</Project>
10 changes: 10 additions & 0 deletions doc/apps/AzureAuthentication/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- We purposely do not include any parent Directory.Build.props files. -->

<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

</Project>
32 changes: 32 additions & 0 deletions doc/apps/AzureAuthentication/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- We purposely do not include any parent Directory.Packages.props files. -->

<PropertyGroup>
<!-- Use SqlClient 7.0.0 Preview 4 if no version was specified. -->
<SqlClientVersion>7.0.0-preview4.26064.3</SqlClientVersion>

<!-- Use AKV Provider 7.0.0 Preview 1 if no version was specified. -->
<AkvProviderVersion>7.0.0-preview1.26064.3</AkvProviderVersion>
</PropertyGroup>

<!-- SqlClient Packages -->
<ItemGroup>
<PackageVersion Include="Microsoft.Data.SqlClient" Version="$(SqlClientVersion)" />
<PackageVersion Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="$(AkvProviderVersion)" />

<!-- Declare a version for the Azure package if one was specified. -->
<PackageVersion
Include="Microsoft.Data.SqlClient.Extensions.Azure"
Version="$(AzureVersion)"
Condition="'$(AzureVersion)' != ''" />
</ItemGroup>

<!-- Other Packages -->
<ItemGroup>
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.3" />
</ItemGroup>

</Project>
85 changes: 85 additions & 0 deletions doc/apps/AzureAuthentication/EntryPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.CommandLine;

namespace Microsoft.Data.SqlClient.Samples.AzureAuthentication;

/// <summary>
/// Contains the application entry point responsible for parsing command-line arguments and
/// delegating execution to <see cref="App"/>.
/// </summary>
public static class EntryPoint
{
/// <summary>
/// Application entry point. Parses command-line arguments and executes the connectivity test.
/// </summary>
/// <param name="args">Command-line arguments.</param>
/// <returns>0 on success; non-zero on failure.</returns>
public static int Main(string[] args)
{
Option<string> connectionStringOption = new("--connection-string", "-c")
{
Description =
"The ADO.NET connection string used to connect to SQL Server. " +
"Supports SQL, Azure AD, and integrated authentication modes.",
Required = true
};

Option<bool> logOption = new("--log-events", "-l")
{
Description = "Enable SqlClient event emission to the console."
};

Option<bool> traceOption = new("--trace", "-t")
{
Description = "Pauses execution to allow dotnet-trace to be attached."
};

Option<bool> verboseOption = new("--verbose", "-v")
{
Description = "Enable verbose output with detailed error information."
};

RootCommand rootCommand = new(
$"""
{App.AppName}
-----------------------------------------

Validates SqlClient connectivity using EntraID (formerly Azure Active Directory)
authentication. Connects to SQL Server using the supplied connection string,
which must specify the authentication method.

Supply specific package versions when building to test different versions of the
SqlClient suite, for example:

-p:SqlClientVersion=7.0.0-preview4
-p:AkvProviderVersion=7.0.1-preview2
-p:AzureVersion=1.0.0-preview1

Current package versions:
SqlClient: {PackageVersions.MicrosoftDataSqlClient}
AKV Provider: {PackageVersions.MicrosoftDataSqlClientAlwaysEncryptedAzureKeyVaultProvider}
Azure: {PackageVersions.AzureExtensionsVersion}
""")
{
connectionStringOption,
logOption,
traceOption,
verboseOption
};

rootCommand.SetAction(parseResult =>
{
App.RunOptions options = new()
{
ConnectionString = parseResult.GetValue(connectionStringOption)!,
LogEvents = parseResult.GetValue(logOption),
Trace = parseResult.GetValue(traceOption),
Verbose = parseResult.GetValue(verboseOption)
};

using App app = new();
return app.Run(options);
});

return rootCommand.Parse(args).Invoke();
}
}
Loading
Loading