I used to use Basic authentication when connecting to Exchange online, but after Microsoft disabled the basic authentication I have not had luck connecting to it. Having it use authentication popup would be ideal.
Code looked something like this:
PSCredential credential = new PSCredential(username, new NetworkCredential("", password).SecurePassword);
WSManConnectionInfo wsEOConnInfo = new WSManConnectionInfo((new Uri("https://outlook.office365.com/powershell-liveid/")),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
wsEOConnInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
try
{
using (Runspace runspace = RunspaceFactory.CreateRunspace(wsEOConnInfo))
{
runspace.Open();
try
{
if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
{
PowerShell ps = PowerShell.Create();
var getLists = new PSCommand();
getLists.AddCommand("Get-DistributionGroup");
ps.Commands = getLists;
ps.Runspace = runspace;
var response = ps.Invoke();
}
}
}
}
I tried replacing the Basic authentication part with Powershell calls, that should bring up external authentication, but that doesn't seem to work with the C# sdk.
Connect-ExchangeOnline -UserPrincipalName "email@email.com"
this just creates error, and even tried to run Install-Module and Import-Module commands, but in the SDK Install-Module command is unknown and Import doesn't help
The error is:
System.Management.Automation.CommandNotFoundException: 'The term 'Get-DistributionGroup' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.'
Is there any way to get access to Exchange Online commands inside C# or do I need to rewrite it all in PowerShell for it to work.
To summarize the problem and provide a sample solution:
You were looking for an alternative to using a remote PowerShell runspace, because your authentication method (Basic
) is no longer supported.
Trying to use a local runspace with a call to Connect-ExchangeOnline
to establish the remote connection encountered two problems:
Your PowerShell SDK project didn't see the required ExchangeOnlineManagement
module, and such projects do not come bundled with Install-Module
for on-demand installation.
Additionally, PowerShell SDK projects do not support PowerShell's interactive features, such as Get-Credential
and Read-Host
, thereby preventing prompting for credentials.
Workarounds can take advantage of the fact that using native .NET features for user interaction is not subject to this limitation, so you can create your own credentials prompt and pass the result to PowerShell later.
For instance, this answer - whose code is integrated below - shows how to present a console-based, masked password prompt that returns a SecureString
instance; alternatively, you can create a GUI dialog.
Simple sample code that demonstrates console-based up-front prompting for a password, constructing a PSCredential
instance, and passing it to PowerShell, written for the PowerShell (Core) SDK (Microsoft.PowerShell.SDK
and .NET 7+:
using System;
using System.Management.Automation;
using System.Security;
// Prompt for a password in the console.
// Note: You're free to create a GUI dialog instead.
var username = "jdoe";
SecureString securePass;
do {
Console.Write($@"Enter password for user '{username}' (Ctrl-C to abort): ");
securePass = GetPassword(); // See function definition below.
} while (securePass.Length == 0);
// Construct a PSCredential instance to pass to PowerShell,
// typically via a -Credential parameter.
var cred = new PSCredential(username, securePass);
using (var ps = PowerShell.Create()) // Create a local runspace.
{
// Pass the PSCredential instance to a sample command.
// Here, we simply output it as-is.
ps.AddCommand("Write-Output").AddArgument(cred);
// Execute the sample command.
foreach (var o in ps.Invoke())
{
// This should echo the full type name of PSCredential, i.e.
// 'System.Management.Automation.PSCredential'
Console.WriteLine("Received from PowerShell: {0}", o);
}
// Print errors, if any.
foreach (var e in ps.Streams.Error)
{
Console.Error.WriteLine(e);
}
}
// Helper function that prompts for a password using masked input,
// and returns a SecureString instance.
// Source: https://stackoverflow.com/a/3404464/45375
static SecureString GetPassword()
{
var pwd = new SecureString();
while (true)
{
ConsoleKeyInfo i = Console.ReadKey(true);
if (i.Key == ConsoleKey.Enter)
{
Console.WriteLine();
break;
}
else if (i.Key == ConsoleKey.Backspace)
{
if (pwd.Length > 0)
{
pwd.RemoveAt(pwd.Length - 1);
Console.Write("\b \b");
}
}
else if (i.KeyChar != '\u0000' ) // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc
{
pwd.AppendChar(i.KeyChar);
Console.Write("*");
}
}
return pwd;
}