Azure resource management API not showing virtual machine state? - c#

So I've been poking around at read-only api access into azure with the resource management api. Right now I'm focusing on Virtual Machines. I've been using this pre-release package with TokenCredentials:
https://www.nuget.org/packages/Microsoft.Azure.Management.Compute/13.0.1-prerelease
I get a bunch of rich info about my vms but I'm missing a pretty criticle piece of data and that's whether the vm is on or off. I've found a couple of meta data properties like InstanceView and Plan to be null when I expected them to be populated. It may because of how I launched my vms, it may be a unfinished or buggy new package, I can't tell. I was thinking InstanceViews statues would tell me what state the vm is in.
https://msdn.microsoft.com/en-us/library/microsoft.azure.management.compute.models.virtualmachineinstanceview.aspx
So I suppose I have to look elsewhere. I did find this older stackoverflow question that may be what I'm looking for:
azure management libraries virtual machine state
However I'm not sure what dll this GetAzureDeyployment is part of or if it's even TokenCredential compatible. Anyone know whats up?

You can use the following c# code to get the power status of your VM.
using System;
using System.Security;
using Microsoft.Azure.Management.Compute;
using Microsoft.Azure.Management.Compute.Models;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Rest;
namespace GetVmARM
{
class Program
{
private static String tenantID = "<your tenant id>";
private static String loginEndpoint = "https://login.windows.net/";
private static Uri redirectURI = new Uri("urn:ietf:wg:oauth:2.0:oob");
private static String clientID = "1950a258-227b-4e31-a9cf-717495945fc2";
private static String subscriptionID = "<your subscription id>";
private static String resource = "https://management.core.windows.net/";
static void Main(string[] args)
{
var token = GetTokenCloudCredentials();
var credential = new TokenCredentials(token);
var computeManagementClient = new ComputeManagementClient(credential);
computeManagementClient.SubscriptionId = subscriptionID;
InstanceViewTypes expand = new InstanceViewTypes();
var vm = computeManagementClient.VirtualMachines.Get("<the resource group name>", "<the VM>", expand);
System.Console.WriteLine(vm.InstanceView.Statuses[1].Code);
System.Console.WriteLine("Press ENTER to continue");
System.Console.ReadLine();
}
public static String GetTokenCloudCredentials(string username = null, SecureString password = null)
{
String authString = loginEndpoint + tenantID;
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
var promptBehaviour = PromptBehavior.Auto;
var userIdentifierType = UserIdentifierType.RequiredDisplayableId;
var userIdentifier = new UserIdentifier("<your azure account>", userIdentifierType);
var authenticationResult = authenticationContext.AcquireToken(resource, clientID, redirectURI, promptBehaviour, userIdentifier);
return authenticationResult.AccessToken;
}
}
}
As you can see in this piece of code, I am using InstanceViewTypes which is not available in the document. This is new in the 13.0.1 pre-release version. But yes, if you adding this to your computeManagementClient.VirtualMachines.Get method, you will be able to get extra information for your VM.
Furthermore, I am using vm.InstanceView.Statuses[1] because vm.InstanceView.Statuses[0] is the ProvisioningState. And, I am not sure if the order is always like this, so you may need to loop through the whole status list.

Related

Error when creating ServiceBus Queue using Azure.Messaging.ServiceBus.Administration

I am (trying) to use this code to create ServiceBus Queue:
using Azure.Messaging.ServiceBus;
using Azure.Messaging.ServiceBus.Administration;
...
class blabla
{
private string connectionString = "Endpoint=sb://XXXX.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=XXXYYY";
private string queueName = "testqueue";
...
public doit()
{
var adminClient = new ServiceBusAdministrationClient(connectionString);
bool queueExists = adminClient.QueueExistsAsync(queueName).Result;
if (!queueExists)
{
var options = new CreateQueueOptions(queueName)
{
DefaultMessageTimeToLive = TimeSpan.FromDays(2),
LockDuration = TimeSpan.FromSeconds(45),
MaxDeliveryCount = 8,
MaxSizeInMegabytes = 2048
};
options.AuthorizationRules.Add(new SharedAccessAuthorizationRule(
"allClaims",
new[] { AccessRights.Manage, AccessRights.Send, AccessRights.Listen }));
QueueProperties createdQueue = adminClient.CreateQueueAsync(options).Result;
}
}
}
but constantly getting this error:
System.AggregateException: One or more errors occurred. (SubCode=40900. Conflict. You're requesting an operation that isn't allowed in the resource's current state. To know more visit https://aka.ms/sbResourceMgrExceptions. . TrackingId:bc79fd98-73c8-4301-b6b9-05d0eae6ed6a_G17, SystemTracker:xxx.servicebus.windows.net:yyy, Timestamp:2021-05-09T00:24:57
Status: 409 (Conflict)
ErrorCode: 40900
Using old (NET) way with NamespaceManager from Microsoft.ServiceBus works with no problems.
var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
if (!namespaceManager.QueueExists(queueName))
{
namespaceManager.CreateQueue(queueName);
}
So, does anyone knows what am I doing wrong here?
*
Below is the updated working code, you need to make sure you have shared access policy with full access.
using Azure.Messaging.ServiceBus.Administration;
using System;
using System.Threading.Tasks;
namespace ServiceBusDemo
{
class Program
{
private static string connectionString = "Endpoint=sb://ns-servicebusshu.servicebus.windows.net/;SharedAccessKeyName=fullAccess;SharedAccessKey=oB+IsK8Aqp0/xfXnF9HCz6x9pqPIOysTXaJofSmHEYs=";
private static string queueName = "testqueue";
async static Task Main(string[] args)
{
await doit();
}
public static async Task doit()
{
var adminClient = new ServiceBusAdministrationClient(connectionString);
bool queueExists = await adminClient.QueueExistsAsync(queueName);
if (!queueExists)
{
var options = new CreateQueueOptions(queueName)
{
DefaultMessageTimeToLive = TimeSpan.FromDays(2),
LockDuration = TimeSpan.FromSeconds(45),
MaxDeliveryCount = 8,
MaxSizeInMegabytes = 2048
};
options.AuthorizationRules.Add(new SharedAccessAuthorizationRule("allClaims", new[] { AccessRights.Manage, AccessRights.Send, AccessRights.Listen }));
QueueProperties createdQueue = await adminClient.CreateQueueAsync(options);
}
}
}
}
Once you ran the application its successfully created the queue as below :
Maybe it's not your case... But if you have a TOPIC with the same name that you try to create your new QUEUE, QueueExistsAsync will return false, but you'll be spitted with this bizarre error at creation time. The fix is easy... changing the queue name or deleting the offending topic.
Sorry for the confusion.
My code (and Rahul Shukla as well) is working now (????).
I had to create a few new shared access policies with full access (????).
The third created started working (??).
The previous 2 I created are still not working (????).
There are no differences between the 3 policies created. Hence the question marks in my answer.
Posted question on MS NET SB forum about 1 out of 3 policies working. No answer/acknowledgment so far.

AmazonS3Client Single connection Vs new connection for each call C#

I am using AmazonS3Client to Read/Write data to S3 Object Storage. In my code i am creating a new connection everytime while doing operations like Read,List Buckets, Upload, Rename, Delete etc. After deploying my application to production i encountered some performance issues. After going throughh few blogs it was recommended to use single amazonS3 client connection. My code below ->
For every below CRUD operations if you see i am creating a new connection and then disposing it by using block. I am planning to have single connection and use it without using block on every call. Does maintaining a single connection good choice ? I have ~400 users accessing application at the same time.
public ObjectFileInfo(string path)
{
StorageClient = ObjectFileManager.GetClient();
objectFileInfo = ObjectFileManager.getFileInfo(StorageClient, path);
}
public class ObjectFileManager
{
public static Amazon.S3.AmazonS3Client GetClient()
{
AmazonS3Config Config = new AmazonS3Config();
AmazonS3Client StorageClient;
Config.RegionEndpoint = null;
Config.ServiceURL = ConfigurationManager.NGDMSobjECSEndPoint;
Config.AllowAutoRedirect = true;
Config.ForcePathStyle = true;
Config.Timeout = TimeSpan.FromMinutes(30);
StorageClient = new AmazonS3Client(ConfigurationManager.NGDMSobjECSUser, ConfigurationManager.NGDMSobjECSKey, Config);
return StorageClient;
}
public static string[] ListBuckets()
{
ListBucketsResponse Response;
//Creating AmazonS3Client and disposing it in using
using (AmazonS3Client StorageClient = GetClient())
{
Response = StorageClient.ListBuckets();
}
var BucketNames = from Bucket in Response.Buckets select Bucket.BucketName;
return BucketNames.ToArray();
}
public static bool DeleteFile(string keyName)
{
var delRequest = new DeleteObjectRequest
{
BucketName = bucketName,
Key = keyName
};
//Creating AmazonS3Client and disposing it in using
using (AmazonS3Client StorageClient = GetClient())
{
StorageClient.DeleteObject(delRequest);
}
return true;
}
}
Planning to use Singleton as below and removing using block ->
class S3ObjectStorageClient
{
/// <summary>
/// Singleton implementation of Object Storage Client
/// </summary>
private S3ObjectStorageClient()
{
}
public static AmazonS3Client Client
{
get
{
return S3Client.clientInstance;
}
}
/// <summary>
/// Nested private class to ensure Singleton
/// </summary>
private class S3Client
{
static S3Client()
{
}
internal static readonly AmazonS3Client clientInstance = ObjectFileManager.GetClient();
}
}
public ObjectFileInfo(string path)
{
StorageClient = S3ObjectStorageClient.Client; //Singleton
objectFileInfo = ObjectFileManager.getFileInfo(StorageClient, path);
}
public static string[] ListBuckets()
{
ListBucketsResponse Response;
//Singleton and removed using block
AmazonS3Client StorageClient = S3ObjectStorageClient.Client;
Response = StorageClient.ListBuckets();
var BucketNames = from Bucket in Response.Buckets select Bucket.BucketName;
return BucketNames.ToArray();
}
public static bool DeleteFile(string keyName)
{
var delRequest = new DeleteObjectRequest
{
BucketName = bucketName,
Key = keyName
};
//Singleton and removed using block
AmazonS3Client StorageClient = S3ObjectStorageClient.Client;
StorageClient.DeleteObject(delRequest);
return true;
}
}
As one of the authors of the AWS .NET SDK I can give a little more context. Under the cover the AmazonS3Client along with all of the other service clients in the SDK it manages a pool of HttpClients which are the expensive object to create. So when you are creating a new AmazonS3Client the SDK is reusing an HttpClient from a pool the SDK is managing.
If you are using a proxy with proxy credentials then the SDK does have to create a new HttpClient each time a service client is created.
An area where there could be potential performance issues with creating service clients all the time is determining the AWS credentials to use when an AWSCredentials object is not passed into the constructor. That means each service client will have to resolve the credentials which if you are using an assume role profile that could cause a lot of extra calls to perform the assume role. Getting credentials from instance metadata is optimized so that only one thread is refreshing those credentials per process.
Actually you can safely reuse it, according to the docs it is not a bad idea to create and reuse a client. But creating a new client is not very expensive:
The best-known aspect of the AWS SDK for .NET are the various service clients that you can use to interact with AWS. Client objects are thread safe, disposable, and can be reused. (Client objects are inexpensive, so you are not incurring a large overhead by constructing multiple instances, but it’s not a bad idea to create and reuse a client.)
Thus, according to this the performance benefits are probably not that huge. But since there is a small cost to creating a new client I would always reuse the client. That said, according to the docs your code
using (AmazonS3Client StorageClient = GetClient())
{
Response = StorageClient.ListBuckets();
}
is not really bad, but just a bit less efficient than using a singleton. If you think it hurts your performance in a noticable way, best bet is to measure it and if it is really the cause refactor to using a singleton.
Both are valid approach but you'll certainly gain code efficiency using a singleton.
Moreover, dependency injection is promoted by AWS as the right pattern when it comes to using clients. For example, new AWS service CodeGuru profiler highlights multiple client instances as a source of optimization.
See also : https://aws.amazon.com/fr/blogs/developer/working-with-dependency-injection-in-net-standard-inject-your-aws-clients-part-1/

How to connect to a database using Active directory Login and MultiFactor Authentication (MFA)

I have already configured my Azure SQL Server so that I am Server admin, my account also has MFA enabled. I was trying to follow this documentation but it doesn't mention anything about Active directory with MFA.
I can use my account and MFA to sign into the server fine using SQL Management studio
Initially I tried (based on the new SqlAuthenticationMethod Enum):
SqlConnection con = new SqlConnection("Server=tcp:myapp.database.windows.net;Database=CustomerDB;Authentication=Active Directory Interactive;Encrypt=True;UID=User#User.co.uk"))
Error:
'Cannot find an authentication provider for
'ActiveDirectoryInteractive'.'
I then saw this about accessing SQL via an Azure application But this is not what I want to do.
This SO question talks about connecting without the provider and setting the Driver in the connection string
SqlConnection con = new SqlConnection("DRIVER={ODBC Driver 17 for SQL Server};Server=tcp:myapp.database.windows.net;Database=CustomerDB;Authentication=Active Directory Interactive;Encrypt=True;UID=User#User.co.uk"))
but I get the error:
'Keyword not supported: 'driver'.'
Is there anyway to write a connection string so that when it tries to connect the Microsoft authentication box pops up to walk the user through Multi factor authentication?
To use Azure AD authentication, your C# program has to register as an Azure AD application. Completing an app registration generates and displays an application ID. Your program has to include this ID to connect. To register and set necessary permissions for your application, go to the Azure portal, select Azure Active Directory > App registrations > New registration.
After the app registration is created, the application ID value is generated and displayed.
Select API permissions > Add a permission.
Select APIs my organization uses > type Azure SQL Database into the search > and select Azure SQL Database.
Select Delegated permissions > user_impersonation > Add permissions.
It seems you have already set an Azure AD admin for your Azure SQL Database.
You can also add a user to the database with the SQL Create User command. An example is CREATE USER [] FROM EXTERNAL PROVIDER. For more information, see here.
Below an example on C#.
using System;
// Reference to Azure AD authentication assembly
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using DA = System.Data;
using SC = System.Data.SqlClient;
using AD = Microsoft.IdentityModel.Clients.ActiveDirectory;
using TX = System.Text;
using TT = System.Threading.Tasks;
namespace ADInteractive5
{
class Program
{
// ASSIGN YOUR VALUES TO THESE STATIC FIELDS !!
static public string Az_SQLDB_svrName = "<Your SQL DB server>";
static public string AzureAD_UserID = "<Your User ID>";
static public string Initial_DatabaseName = "<Your Database>";
// Some scenarios do not need values for the following two fields:
static public readonly string ClientApplicationID = "<Your App ID>";
static public readonly Uri RedirectUri = new Uri("<Your URI>");
public static void Main(string[] args)
{
var provider = new ActiveDirectoryAuthProvider();
SC.SqlAuthenticationProvider.SetProvider(
SC.SqlAuthenticationMethod.ActiveDirectoryInteractive,
//SC.SqlAuthenticationMethod.ActiveDirectoryIntegrated, // Alternatives.
//SC.SqlAuthenticationMethod.ActiveDirectoryPassword,
provider);
Program.Connection();
}
public static void Connection()
{
SC.SqlConnectionStringBuilder builder = new SC.SqlConnectionStringBuilder();
// Program._ static values that you set earlier.
builder["Data Source"] = Program.Az_SQLDB_svrName;
builder.UserID = Program.AzureAD_UserID;
builder["Initial Catalog"] = Program.Initial_DatabaseName;
// This "Password" is not used with .ActiveDirectoryInteractive.
//builder["Password"] = "<YOUR PASSWORD HERE>";
builder["Connect Timeout"] = 15;
builder["TrustServerCertificate"] = true;
builder.Pooling = false;
// Assigned enum value must match the enum given to .SetProvider().
builder.Authentication = SC.SqlAuthenticationMethod.ActiveDirectoryInteractive;
SC.SqlConnection sqlConnection = new SC.SqlConnection(builder.ConnectionString);
SC.SqlCommand cmd = new SC.SqlCommand(
"SELECT '******** MY QUERY RAN SUCCESSFULLY!! ********';",
sqlConnection);
try
{
sqlConnection.Open();
if (sqlConnection.State == DA.ConnectionState.Open)
{
var rdr = cmd.ExecuteReader();
var msg = new TX.StringBuilder();
while (rdr.Read())
{
msg.AppendLine(rdr.GetString(0));
}
Console.WriteLine(msg.ToString());
Console.WriteLine(":Success");
}
else
{
Console.WriteLine(":Failed");
}
sqlConnection.Close();
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Connection failed with the following exception...");
Console.WriteLine(ex.ToString());
Console.ResetColor();
}
}
} // EOClass Program.
/// <summary>
/// SqlAuthenticationProvider - Is a public class that defines 3 different Azure AD
/// authentication methods. The methods are supported in the new .NET 4.7.2.
/// .
/// 1. Interactive, 2. Integrated, 3. Password
/// .
/// All 3 authentication methods are based on the Azure
/// Active Directory Authentication Library (ADAL) managed library.
/// </summary>
public class ActiveDirectoryAuthProvider : SC.SqlAuthenticationProvider
{
// Program._ more static values that you set!
private readonly string _clientId = Program.ClientApplicationID;
private readonly Uri _redirectUri = Program.RedirectUri;
public override async TT.Task<SC.SqlAuthenticationToken>
AcquireTokenAsync(SC.SqlAuthenticationParameters parameters)
{
AD.AuthenticationContext authContext =
new AD.AuthenticationContext(parameters.Authority);
authContext.CorrelationId = parameters.ConnectionId;
AD.AuthenticationResult result;
switch (parameters.AuthenticationMethod)
{
case SC.SqlAuthenticationMethod.ActiveDirectoryInteractive:
Console.WriteLine("In method 'AcquireTokenAsync', case_0 == '.ActiveDirectoryInteractive'.");
result = await authContext.AcquireTokenAsync(
parameters.Resource, // "https://database.windows.net/"
_clientId,
_redirectUri,
new AD.PlatformParameters(AD.PromptBehavior.Auto),
new AD.UserIdentifier(
parameters.UserId,
AD.UserIdentifierType.RequiredDisplayableId));
break;
case SC.SqlAuthenticationMethod.ActiveDirectoryIntegrated:
Console.WriteLine("In method 'AcquireTokenAsync', case_1 == '.ActiveDirectoryIntegrated'.");
result = await authContext.AcquireTokenAsync(
parameters.Resource,
_clientId,
new AD.UserCredential());
break;
case SC.SqlAuthenticationMethod.ActiveDirectoryPassword:
Console.WriteLine("In method 'AcquireTokenAsync', case_2 == '.ActiveDirectoryPassword'.");
result = await authContext.AcquireTokenAsync(
parameters.Resource,
_clientId,
new AD.UserPasswordCredential(
parameters.UserId,
parameters.Password));
break;
default: throw new InvalidOperationException();
}
return new SC.SqlAuthenticationToken(result.AccessToken, result.ExpiresOn);
}
public override bool IsSupported(SC.SqlAuthenticationMethod authenticationMethod)
{
return authenticationMethod == SC.SqlAuthenticationMethod.ActiveDirectoryIntegrated
|| authenticationMethod == SC.SqlAuthenticationMethod.ActiveDirectoryInteractive
|| authenticationMethod == SC.SqlAuthenticationMethod.ActiveDirectoryPassword;
}
} // EOClass ActiveDirectoryAuthProvider.
} // EONamespace. End of entire program source code.
The example above relies on the Microsoft.IdentityModel.Clients.ActiveDirectory DLL assembly.
To install this package, in Visual Studio, select Project > Manage NuGet Packages. Search for and install Microsoft.IdentityModel.Clients.ActiveDirectory.
Starting in .NET Framework version 4.7.2, the enum SqlAuthenticationMethod has a new value: ActiveDirectoryInteractive.
The only way I have found to login using Active Directory and MFA and cache the token is to use #Alberto's method
I did also find another way which would ask for login credentials every time which is to use this connection string:
OdbcConnection con = new OdbcConnection("Driver={ODBC Driver 17 for SQL Server};SERVER=tcp:myserver.database.windows.net;DATABASE=MyDb;Authentication=ActiveDirectoryInteractive;UID=User#Userco.uk")
Improving the code posted by #alberto. I must say for something so fundamental in the modern world this is unbelievably undocumented. Anyway here's the improved Provider code.
This code also requires you to target .Net Framework 4.7.2 or greater
Firstly follow #alberto's code.. I did find one extra unmentioned step is that you need to also configure a Platform for your app in azure on the authentication tab to look like:
Add these two classes to your project:
ActiveDirectoryAuthProvider
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Data.SqlClient;
namespace SQLAzureConnectivity
{
public class ActiveDirectoryAuthProvider : SqlAuthenticationProvider
{
private string _clientId { get; set; }
private Uri _redirectURL { get; set; } = new Uri("https://login.microsoftonline.com/common/oauth2/nativeclient");
public ActiveDirectoryAuthProvider(string clientId)
{
_clientId = clientId;
}
//https://learn.microsoft.com/en-us/azure/sql-database/active-directory-interactive-connect-azure-sql-db#c-code-example
public override async Task<System.Data.SqlClient.SqlAuthenticationToken> AcquireTokenAsync(System.Data.SqlClient.SqlAuthenticationParameters parameters)
{
AuthenticationContext authContext = new AuthenticationContext(parameters.Authority, new FilesBasedAdalV3TokenCache(".\\Token.dat"));
authContext.CorrelationId = parameters.ConnectionId;
AuthenticationResult result = null;
switch (parameters.AuthenticationMethod)
{
case System.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryInteractive:
Console.WriteLine("In method 'AcquireTokenAsync', case_0 == '.ActiveDirectoryInteractive'.");
try
{
result = await authContext.AcquireTokenSilentAsync(parameters.Resource, _clientId);
}
catch (AdalException adalException)
{
if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently || adalException.ErrorCode == AdalError.InteractionRequired)
{
result = await authContext.AcquireTokenAsync(parameters.Resource, _clientId, _redirectURL, new PlatformParameters(PromptBehavior.Auto));
//result = await authContext.AcquireTokenAsync(parameters.Resource, _clientId, _redirectURL, new PlatformParameters(PromptBehavior.Auto), new UserIdentifier(parameters.UserId, UserIdentifierType.RequiredDisplayableId));
}
}
break;
case System.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryIntegrated:
Console.WriteLine("In method 'AcquireTokenAsync', case_1 == '.ActiveDirectoryIntegrated'.");
result = await authContext.AcquireTokenAsync(parameters.Resource, _clientId, new UserCredential());
break;
case System.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryPassword:
Console.WriteLine("In method 'AcquireTokenAsync', case_2 == '.ActiveDirectoryPassword'.");
result = await authContext.AcquireTokenAsync(parameters.Resource, _clientId, new UserPasswordCredential(parameters.UserId, parameters.Password));
break;
default:
throw new InvalidOperationException();
}
return new System.Data.SqlClient.SqlAuthenticationToken(result.AccessToken, result.ExpiresOn);
}
public override bool IsSupported(System.Data.SqlClient.SqlAuthenticationMethod authenticationMethod)
{
return authenticationMethod == System.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryIntegrated
|| authenticationMethod == System.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryInteractive
|| authenticationMethod == System.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryPassword;
}
}
}
FilesBasedAdalV3TokenCache
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.IO;
using System.Security.Cryptography;
namespace SQLAzureConnectivity
{
// This is a simple persistent cache implementation for an ADAL V3 desktop application
public class FilesBasedAdalV3TokenCache : TokenCache
{
public string CacheFilePath { get; }
private static readonly object FileLock = new object();
// Initializes the cache against a local file.
// If the file is already present, it loads its content in the ADAL cache
public FilesBasedAdalV3TokenCache(string filePath)
{
CacheFilePath = filePath;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
lock (FileLock)
{
this.DeserializeAdalV3(ReadFromFileIfExists(CacheFilePath));
}
}
// Empties the persistent store.
public override void Clear()
{
base.Clear();
File.Delete(CacheFilePath);
}
// Triggered right before ADAL needs to access the cache.
// Reload the cache from the persistent store in case it changed since the last access.
void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
this.DeserializeAdalV3(ReadFromFileIfExists(CacheFilePath));
}
}
// Triggered right after ADAL accessed the cache.
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (this.HasStateChanged)
{
lock (FileLock)
{
// reflect changes in the persistent store
WriteToFileIfNotNull(CacheFilePath, this.SerializeAdalV3());
// once the write operation took place, restore the HasStateChanged bit to false
this.HasStateChanged = false;
}
}
}
/// <summary>
/// Read the content of a file if it exists
/// </summary>
/// <param name="path">File path</param>
/// <returns>Content of the file (in bytes)</returns>
private byte[] ReadFromFileIfExists(string path)
{
byte[] protectedBytes = (!string.IsNullOrEmpty(path) && File.Exists(path))
? File.ReadAllBytes(path) : null;
byte[] unprotectedBytes = (protectedBytes != null)
? ProtectedData.Unprotect(protectedBytes, null, DataProtectionScope.CurrentUser) : null;
return unprotectedBytes;
}
/// <summary>
/// Writes a blob of bytes to a file. If the blob is <c>null</c>, deletes the file
/// </summary>
/// <param name="path">path to the file to write</param>
/// <param name="blob">Blob of bytes to write</param>
private static void WriteToFileIfNotNull(string path, byte[] blob)
{
if (blob != null)
{
byte[] protectedBytes = ProtectedData.Protect(blob, null, DataProtectionScope.CurrentUser);
File.WriteAllBytes(path, protectedBytes);
}
else
{
File.Delete(path);
}
}
}
}
Then before using a SQLConnection write these two lines:
var provider = new ActiveDirectoryAuthProvider("ClientID from the Azure app you set up earlier");
SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, provider);
References:
https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Token-cache-serialization
https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Acquiring-tokens-interactively---Public-client-application-flows#properties-or-platformparameters-constructors-parameters-common-to-most-platforms
https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/AcquireTokenSilentAsync-using-a-cached-token#recommended-pattern-to-acquire-a-token
https://learn.microsoft.com/en-us/azure/sql-database/active-directory-interactive-connect-azure-sql-db
As mentioned elsewhere, you can use ODBC to connect, without registering your app in the Azure Portal. The interactive prompt will be shown whenever a new connection is added to the pool. Thus, even if you open multiple ODBC connections using the same connection string, you will only see the prompt once within your application lifecycle (or until the connection pool is recycled).
If you don't want to use ODBC, you may also use OLE DB with the MSOLEDBSQL driver, which has similar (or better) performance than the native SQL Client provider (which is deprecated and shouldn't be used anyway):
using System.Data.OleDb;
...
OleDbConnection con = new OleDbConnection("Provider=MSOLEDBSQL;Data Source=sqlserver.database.windows.net;User ID=user#domain.com;Initial Catalog=database;Authentication=ActiveDirectoryInteractive");
This may not be the best place to put this answer, as is it is specific to unit testing sql server and visual studio (community,prof,ent) -- https://youtu.be/OZiTKfNSXh4 # 1:10 -- via mfa interactive using #Dan answer.
The problem is that generating a c#/sql unit test project can be done using interactive connection. But running any unit test will fail because mfa interactive is not supported by SqlClient provider. Below is a work-around.
New file OleDatabaseTestService.cs
using Microsoft.Data.Tools.Schema.Sql.UnitTesting;
using System.Data.OleDb;
namespace [YourNamespace]Tests
{
public class OleDatabaseTestService : SqlDatabaseTestService
{
static OleDatabaseTestService()
{
SetupConext();
}
private static ConnectionContext _contextExecution = null;
private static ConnectionContext _contextPrivileged = null;
public static ConnectionContext ContextExecution { get; set; } = null;
public static ConnectionContext ContextPrivileged { get; set; } = null;
public override ConnectionContext OpenExecutionContext()
{
return ContextExecution;
}
public override ConnectionContext OpenPrivilegedContext()
{
return ContextPrivileged == null ? ContextExecution : ContextPrivileged;
}
// TODO: This can be a written a lot better - please edit this SO if you wish to help
protected static ConnectionContext SetupConext()
{
var context = new ConnectionContext();
context.Provider = OleDbFactory.Instance;
var connection = context.Provider.CreateConnection();
// TODO: Drive the connection string from app.config interactive connection string (wizard creates interactive correctly, but not supported by SqlClient provider)
// var connectionSection = (SqlUnitTestingSection)ConfigurationManager.GetSection("SqlUnitTesting"); // DbConnection connection = new OleDbConnection(
connection.ConnectionString = "Provider=MSOLEDBSQL;Data Source=[azure_database_name].database.windows.net;Initial Catalog=[initial db];User ID=[email];Authentication=ActiveDirectoryInteractive"; // + connectionSection.ExecutionContext.ConnectionString;
connection.Open();
context.Connection = connection;
ContextExecution = context;
ContextPrivileged = context;
return context;
}
}
}
Change to SqlDatabaseSetup.cs
using Microsoft.Data.Tools.Schema.Sql.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace [YourNamespace]Tests
{
[TestClass()]
public class SqlDatabaseSetup
{
[AssemblyInitialize()]
public static void InitializeAssembly(TestContext ctx)
{
var service = new OleDatabaseTestService();
SqlDatabaseTestClass.TestService = service;
SqlDatabaseTestClass.TestService.DeployDatabaseProject();
SqlDatabaseTestClass.TestService.GenerateData();
}
}
}
Please add a comment on where this would best be moved to. Or if
someone prefers this as a Question/self-Answered on its own (no need
to waste points).

How to get Unique Display name for VSTS/TFS identity?

I am a writing a .Net application using the VSTS/TFS Rest .Net libraries and in one place I need to update workitems' System.AssignedTo field values and while I do want to adhere to the new(ish), unique displayname rules for identity work item fields, I have a hard time finding a method to get the Unique display name(s) for given identities.
The old / client object model does have an explicit helper method to get these unique names, but I have not found any rest endpoint nor client api method that would provide the same.
So I am wondering, given a list of identities, how do I get their corresponding unique display names which I can use to unambiguously set identity work item fields?
String collectionUri = "http://collectionurl/";
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
VssConnection connection = new VssConnection(new Uri(collectionUri), creds);
TeamHttpClient thc = connection.GetClient<TeamHttpClient>();
List<IdentityRef> irs = thc.GetTeamMembersAsync("ProjectName","TeamName").Result;
foreach (IdentityRef ir in irs)
{
Console.WriteLine(ir.UniqueName);
Console.WriteLine(ir.DisplayName);
}
You could try the code below to get unique name:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
TfsConfigurationServer tcs = new TfsConfigurationServer(new Uri("http://tfsserver:8080/tfs"));
IIdentityManagementService ims = tcs.GetService<IIdentityManagementService>();
TeamFoundationIdentity tfi = ims.ReadIdentity(IdentitySearchFactor.AccountName, "[TEAM FOUNDATION]\\Team Foundation Valid Users", MembershipQuery.Expanded, ReadIdentityOptions.None);
TeamFoundationIdentity[] ids = ims.ReadIdentities(tfi.Members, MembershipQuery.None, ReadIdentityOptions.None);
foreach (TeamFoundationIdentity id in ids)
{
if (id.Descriptor.IdentityType == "System.Security.Principal.WindowsIdentity")
{
Console.WriteLine(id.DisplayName);
Console.WriteLine(id.UniqueName);
}
}
Console.ReadLine();
}
}
}
foreach (var workItem in workItems)
{
if (workItem.Fields.ContainsKey("System.AssignedTo"))
{
var person = (IdentityRef)workItem.Fields["System.AssignedTo"];
string codereview_reviewer = person.DisplayName;
Console.WriteLine(codereview_reviewer);
}
}

Connect and check out methods

I have a method to connect to tfs and check out files. I have to separate it into 2 methods because they won't occur consecutively. But I am not sure how to separate it into 2 methods because if I did the check out, it means I have to get the Credentials and project collection again?
public static void Connect(String server, string path)
{
try
{
Uri serverUri = new Uri(server + "/tfs");
ICredentialsProvider credentials = new UICredentialsProvider();
TfsTeamProjectCollection tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(serverUri, credentials);
tpc.EnsureAuthenticated();
VersionControlServer versionControl = tpc.GetService<VersionControlServer>();
Workspace workspace = versionControl.TryGetWorkspace(path);
workspace.PendEdit(path);
}
I would suggest that you don't make the function static, then you can simply store the variables at class level (you can still store them at class level if they are static, but at least this way you have some scope as to the lifespan:
public class TfsWrapper
{
private TfsTeamProjectCollection tpc = null;
private VersionControlServer versionControl = null;
public TfsWrapper(string server, ...)
{
try
{
Uri serverUri = new Uri(server + "/tfs");
ICredentialsProvider credentials = new UICredentialsProvider();
tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(serverUri, credentials);
tpc.EnsureAuthenticated();
versionControl = tpc.GetService<VersionControlServer>();
}
}
public void Checkout(string path)
{
Workspace workspace = versionControl.TryGetWorkspace(path);
workspace.PendEdit(path);
}
I suggest you to use this code, he treats encapsulation & refactoring aspect between lot of servers & credentials
link : http://blogs.msdn.com/b/buckh/archive/2012/03/10/team-foundation-version-control-client-api-example-for-tfs-2010-and-newer.aspx

Categories

Resources