Connecting with Azure SQL Managed Instance Managed Identity from MSBuild to run SQL scripts

Sam Larko 1 Reputation point
2020-11-16T23:09:49.717+00:00

I have an application that has a complex MSBuild system that executes .SQL scripts against SQL databases stored in a SQL Managed Instance (SMI) in Azure Gov. This is process is being executed from a GitLab CI/CD pipeline. I can execute it without issue using SQL Auth, but security rules from the client forbid using SQL Auth. The SMI has a Managed Identity, located under Enterprise Applications in AAD, that has all the roles needed to execute the scripts, but I get errors when I use ActiveDirectoryInteractive authentication to connect.

I've tried a few ways to make this work. I'm open to any of the following working. Any ideas? #1 is the preferred option if I'm wrong about it being a limitation of SQLCMD. #3 would the next preferred.

  1. SQLCMD doesn't support ActiveDirectoryInteractive auth without a password.
  2. A PowerShell script that uses .Net objects. This failed with the following error, sample connection string, sample call, and PS function:
    Error:

Exception calling "ExecuteNonQuery" with "1" argument(s): "Failed to connect to server tcp:tcp:myservername.database.usgovcloudapi.net,1433." At C:\MyCodePath\Build\MsBuild\Core\Execute-MsBuildSqlCommand.ps1:48 char:5 $server.ConnectionContext.ExecuteNonQuery($script)

 **Sample Connection String**:

Server=tcp:myservername.database.usgovcloudapi.net,1433;UID=My-SMI-Managed-Identity-Name;Authentication=Active Directory Interactive;

 **Sample call**

powershell.exe -command "& { C:\MyCodePath\Build\MsBuild\Core\Execute-MsBuildSqlCommand.ps1 -DataBaseServerConnectionString 'Server=tcp:tcp:myservername.database.usgovcloudapi.net,1433;UID=;Authentication=Active Directory Interactive;' -ScriptFilePath 'C:\MyCodePath\Database\Common\DbSql\Drop\DROP_CHECK_CONSTRAINTS.sql' -LibFolderPath 'C:\MyCodePath\Build\Lib'}"

 **PowerShell Function**
[CmdletBinding()]
        param(
            [Parameter(Mandatory=$true)]
            [string] $DataBaseServerConnectionString,

            [Parameter(Mandatory=$true)]
            [string] $LibFolderPath,

            [Parameter(Mandatory=$true)]
            [string] $ScriptFilePath

            )

              #Write-Output "Execute PS script with Conn String var: "
              #Write-Output $DataBaseServerConnectionString

            Write-Output $DataBaseServerConnectionString
            Write-Output $LibFolderPath
            Write-Output $ScriptFilePath

            <# These were added to the Build/Lib folder from these packages: 
                https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects
                https://www.nuget.org/packages/Microsoft.Data.SqlClient/
                https://www.nuget.org/packages/Microsoft.Azure.Services.AppAuthentication/1.2.0-preview2
            #>

            Get-ChildItem -Path "$LibFolderPath\PowerShellDBDeploy" | Foreach-Object {
                [System.Reflection.Assembly]::LoadFrom($_.FullName)
            }

            $scriptBaseName = (Get-ChildItem -Path $ScriptFilePath).BaseName
            $script = Get-Content $ScriptFilePath

            Test-Path "$LibFolderPath\PowerShellDBDeploy"

            $connection = New-Object Microsoft.Data.SqlClient.SQLConnection($DataBaseServerConnectionString)
            Write-Output "Connection"
            $connection
            $serverConnection = New-Object Microsoft.SqlServer.Management.Common.ServerConnection($connection)
            Write-Output "Server Connection"
            $serverConnection
            $server = New-Object Microsoft.SqlServer.Management.Smo.Server($serverConnection)
            Write-Output "Server"
            $server
            $server.ConnectionContext.ExecuteNonQuery($script)

            <# $sqlQueries = $script.Split

            $connection.Open();

            $transaction = $connection.BeginTransaction($scriptBaseName);
            $cmd = new SqlCommand("query", $connection);
            $cmd.Transaction  = $transaction;

            try
            {
                foreach (var query in sqlqueries)
                {
                    cmd.CommandText = query;
                    cmd.ExecuteNonQuery();
                }
                    transaction.Commit();
            }
            catch
            {
                transaction.Rollback();
            }
            finally
            {
                $connection.Close();
            } #>
  1. Created a .Net 4.7.2 custom MSBuild Task that's similar to the PowerShell script. The connection string is the same as above. Here's the rest of the information:
    Error

error MSB4018: Microsoft.SqlServer.Management.Common.ConnectionFailureException: Failed to connect to server tcp:myservername.database.usgovcloudapi.net,1433. ---> System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.

 **Usage**

<ExecuteAzureSqlQuery ServicePrincipalName="My-SMI-Managed-Identity-Name" ServerName="$(ManagedInstanceFQDN)" ScriptFilePath="$(DatabaseFolder)\Common\DbSql\Drop\DROP_CHECK_CONSTRAINTS.sql" />

 **Custom Task code** (tried with and without the AccessToken)
public class ExecuteAzureSqlQuery : Microsoft.Build.Utilities.Task
 {


 #region Properties
 /// <summary>
 /// Gets or sets the files to update.
 /// </summary>
 /// <value>The files.</value>
 public ITaskItem ServicePrincipalName { get; set; }

 public ITaskItem ServerName { get; set; }
 /// <summary>
 /// Gets or sets the location to save the updated files to
 /// <remarks>If this is empty, then the files are save to the same directory as they originated from</remarks>
 /// </summary>
 /// <value>The output folder to place updated files into.</value>
 public ITaskItem ScriptFilePath { get; set; }
 #endregion

 public override bool Execute()
 {
 Log.LogMessage(MessageImportance.Normal, $"Server: {ServerName.ItemSpec}, UID: {ServicePrincipalName.ItemSpec}");
 string connectionString = $"Server=tcp:{ServerName.ItemSpec},1433;UID={ServicePrincipalName.ItemSpec};Authentication=Active Directory Interactive;";
 var connection = new SqlConnection(connectionString);
 //connection.AccessToken = (new AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.usgovcloudapi.net").Result;
 var server = new Server(new ServerConnection(connection));

 FileInfo fileInfo = new FileInfo(ScriptFilePath.ItemSpec);
 string script = fileInfo.OpenText().ReadToEnd();

 server.ConnectionContext.ExecuteNonQuery(script);

 return true;
 }
 }
Azure SQL Database
Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
21,689 questions
{count} votes

3 answers

Sort by: Most helpful
  1. 2020-11-17T16:57:13.83+00:00

    Hello @Sam Larko , you've to remove Authentication=Active Directory Interactive from your connection string and retrieve an access token (dismiss all numbered steps but 2) that will be set in the $connection.AccessToken property. For token retrieval you can use ManagedIdentityCredential from the Azure.Identity nuget package.

    Let us know if this answer was helpful to you. If so, please remember to mark it as the answer so that others in the community with similar questions can more easily find a solution.

    1 person found this answer helpful.
    0 comments No comments

  2. Sam Larko 1 Reputation point
    2020-11-17T20:14:50.34+00:00

    anonymous user-msft , I've tried using the following code, but it fails on the connection.AccessToken line. The error is below, too. Am I using the right scope? How do I know what identity it's trying to use?

    string connectionString = $"Data Source=tcp:{ServerName.ItemSpec},1433;Initial Catalog=master;";
                Log.LogMessage(MessageImportance.Normal, $"Connection String: {connectionString}");
                var connection = new SqlConnection(connectionString);
                connection.AccessToken = (new ManagedIdentityCredential()).GetToken(new TokenRequestContext(new string[] { "https://database.usgovcloudapi.net//.default" })).Token;
                Log.LogMessage(MessageImportance.Normal, $"Access Token: {connection.AccessToken}");
                var server = new Server(new ServerConnection(connection));
    

    error MSB4018: Azure.Identity.CredentialUnavailableException: ManagedIdentityCredential authentication unavailable. The requested identity has not been assigned to this resource.

    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: The "ExecuteAzureSqlQuery" task failed unexpectedly. [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Azure.Identity.CredentialUnavailableException: ManagedIdentityCredential authentication unavailable. The requested identity has not been assigned to this resource. [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Status: 400 (Bad Request) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Content: [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: {"error":"invalid_request","error_description":"Identity not found"} [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Headers: [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Content-Length: 68 [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Content-Type: application/json; charset=utf-8 [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Date: Tue, 17 Nov 2020 19:45:19 GMT [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: Server: IMDS/Valid-IP-Address [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Azure.Identity.ImdsManagedIdentitySource.<HandleResponseAsync>d__11.MoveNext() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: --- End of stack trace from previous location where exception was thrown --- [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) > [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Azure.Identity.ManagedIdentitySource.<AuthenticateAsync>d__8.MoveNext() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: --- End of stack trace from previous location where exception was thrown --- [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Azure.Identity.ManagedIdentityClient.<AuthenticateAsync>d__12.MoveNext() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: --- End of stack trace from previous location where exception was thrown --- [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Azure.Identity.ManagedIdentityCredential.<GetTokenImplAsync>d__9.MoveNext() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: --- End of stack trace from previous location where exception was thrown --- [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Azure.Identity.CredentialDiagnosticScope.FailWrapAndThrow(Exception ex) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Azure.Identity.ManagedIdentityCredential.<GetTokenImplAsync>d__9.MoveNext() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: --- End of stack trace from previous location where exception was thrown --- [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Azure.Identity.ManagedIdentityCredential.GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at MsBuild.Azure.Sql.ExecuteAzureSqlQuery.Execute() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]
    C:\MyCodePath\Build\MsBuild\Core\common.db.msbuild(756,3): error MSB4018: at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext() [C:\MyCodePath\Build\Msbuild\Environments\dev_env.msbuild]


  3. 2020-11-20T15:17:06.097+00:00

    Hi @Sam Larko . You have to set the environment variables before running the code.

    Linux

       export AZURE_TENANT_ID="<tenant id>"  
       export AZURE_CLIENT_ID="<client id>"  
    

    And so on

    Windows CMD

       SET AZURE_TENANT_ID="<tenant id>"  
       SET AZURE_CLIENT_ID="<client id>"  
    

    And so on

    Powershell

       $env:AZURE_TENANT_ID="<tenant id>"  
       $env:AZURE_CLIENT_ID="<client id>"  
    

    And so on


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.