While not as popular these days many .net developers may have in the past used Managed code to run Exchange Online PowerShell cmdlets to do things like assign Mailbox Permissions or run other EXO PowerShell Cmdlets to get reporting information where no other alternatives where available (or are still available). The majority of these code bases are most likely using basic authentication using something like
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
System.Security.SecureString secureString = new System.Security.SecureString(); | |
string myPassword = "password"; | |
foreach (char c in myPassword) | |
secureString.AppendChar(c); | |
PSCredential credential = new PSCredential("glen@domain.com", secureString); | |
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://ps.outlook.com/PowerShell-LiveID?PSVersion=2.0"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential); | |
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic; | |
connectionInfo.SkipCACheck = true; | |
connectionInfo.SkipCNCheck = true; | |
connectionInfo.MaximumConnectionRedirectionCount = 4; | |
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo); | |
runspace.Open(); |
Or maybe some of the examples in https://docs.microsoft.com/en-us/exchange/client-developer/management/how-to-get-a-list-of-mail-users-by-using-the-exchange-management-shell
In this post I'm going to cover how to change your existing code, you might want to consider however making use of some of the new ExchangeV2 Powershell module functionality to improve performance and security . But to migrate existing code to use oAuth from Basic Authentication is relatively straight forward
- You will need some code to do the Authentication, for this I'm going to use the MSAL library because its both the recommended library from Microsoft and its easy to use.
- You should create your own Azure App registration and consent to it that has the Exchange.Manage Permissions eg
(If you can't create your own app registration you can use the well-known ClientId from the V2 PowerShell Module which I've used in the below samples).
Once you have your authentication code generating a Token you then use that as the Password in the PSCrednetial object you pass in the WSManConnectionInfo object. The one thing you need to change is the WSManConnection URI to include the parameters DelegatedOrg which should be set to your domain and add BasicAuthToOAuthConversion=true eg so your connection string should look like
https://outlook.office365.com/powershell-liveid?DelegatedOrg=youdomain.onmicrosoft.com&BasicAuthToOAuthConversion=true
eg an Interactive Auth sample to run Get-Mailbox would look like
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
string MailboxName = "gscales@datarumble.com"; | |
string scope = "https://outlook.office365.com/.default"; | |
string ClientId = "a0c73c16-a7e3-4564-9a95-2bdf47383716"; | |
PublicClientApplicationBuilder pcaConfig = PublicClientApplicationBuilder.Create(ClientId); | |
pcaConfig.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs, false); | |
var TokenResult = pcaConfig.Build().AcquireTokenInteractive(new[] { scope }) | |
.WithPrompt(Prompt.SelectAccount) | |
.WithLoginHint(MailboxName).ExecuteAsync().Result; | |
System.Security.SecureString secureString = new System.Security.SecureString(); | |
foreach (char c in ("bearer " + TokenResult.AccessToken)) | |
secureString.AppendChar(c); | |
String WSManURIConnectionString = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + MailboxName.Split('@')[1] + "&BasicAuthToOAuthConversion=true"; | |
PSCredential credential = new PSCredential(MailboxName, secureString); | |
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(WSManURIConnectionString), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential); | |
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic; | |
connectionInfo.SkipCACheck = true; | |
connectionInfo.SkipCNCheck = true; | |
connectionInfo.MaximumConnectionRedirectionCount = 4; | |
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo); | |
runspace.Open(); | |
// Make a Get-Mailbox requst using the Server Argument | |
Command gmGetMailbox = new Command("get-mailbox"); | |
gmGetMailbox.Parameters.Add("ResultSize", "Unlimited"); | |
Pipeline plPileLine = runspace.CreatePipeline(); | |
plPileLine.Commands.Add(gmGetMailbox); | |
Collection<PSObject> RsResultsresults = plPileLine.Invoke(); | |
Dictionary<string, PSObject> gmResults = new Dictionary<string, PSObject>(); | |
foreach (PSObject obj in RsResultsresults) | |
{ | |
Console.WriteLine(obj.Members["WindowsEmailAddress"].Value.ToString()); | |
} | |
Command gmGetUser = new Command("get-user"); | |
plPileLine.Stop(); | |
plPileLine.Dispose(); |
If you need your code to run non-interactively with a set of credentials you can use the ROPC grant like
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PSCredential pSCredential = new PSCredential("user@blah.onmicrosoft.com", new NetworkCredential("", "pass##").SecurePassword); | |
string MailboxName = pSCredential.UserName; | |
string scope = "https://outlook.office365.com/.default"; | |
string ClientId = "a0c73c16-a7e3-4564-9a95-2bdf47383716"; | |
HttpClient Client = new HttpClient(); | |
var TenantId = ((dynamic)JsonConvert.DeserializeObject(Client.GetAsync("https://login.microsoftonline.com/" + MailboxName.Split('@')[1] + "/v2.0/.well-known/openid-configuration").Result.Content.ReadAsStringAsync().Result)).authorization_endpoint.ToString().Split('/')[3]; | |
PublicClientApplicationBuilder pcaConfig = PublicClientApplicationBuilder.Create(ClientId); | |
pcaConfig.WithTenantId(TenantId); | |
var TokenResult = pcaConfig.Build().AcquireTokenByUsernamePassword(new[] { scope }, pSCredential.UserName, pSCredential.Password).ExecuteAsync().Result; | |
System.Security.SecureString secureString = new System.Security.SecureString(); | |
foreach (char c in ("bearer " + TokenResult.AccessToken)) | |
secureString.AppendChar(c); | |
String WSManURIConnectionString = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + MailboxName.Split('@')[1] + "&BasicAuthToOAuthConversion=true"; | |
PSCredential credential = new PSCredential(MailboxName, secureString); | |
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(WSManURIConnectionString), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential); | |
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic; | |
connectionInfo.SkipCACheck = true; | |
connectionInfo.SkipCNCheck = true; | |
connectionInfo.MaximumConnectionRedirectionCount = 4; | |
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo); | |
runspace.Open(); | |
// Make a Get-Mailbox requst using the Server Argument | |
Command gmGetMailbox = new Command("get-mailbox"); | |
gmGetMailbox.Parameters.Add("ResultSize", "Unlimited"); | |
Pipeline plPileLine = runspace.CreatePipeline(); | |
plPileLine.Commands.Add(gmGetMailbox); | |
Collection<PSObject> RsResultsresults = plPileLine.Invoke(); | |
Dictionary<string, PSObject> gmResults = new Dictionary<string, PSObject>(); | |
foreach (PSObject obj in RsResultsresults) | |
{ | |
Console.WriteLine(obj.Members["WindowsEmailAddress"].Value.ToString()); | |
} | |
Command gmGetUser = new Command("get-user"); | |
plPileLine.Stop(); | |
plPileLine.Dispose(); |