Our code currently uses WSManConnectionInfo class to connect to O365. We use basic auth and are trying to upgrade to modern auth. I turned off basic auth in my tenant. Following this guide here, https://www.michev.info/Blog/Post/2997/connecting-to-exchange-online-powershell-via-client-secret-fl..., I am able to connect successfully in PowerShell by getting an access token and using New-PSSession cmdlet. I use the following commands:
Add-Type -Path 'C:\Program Files\WindowsPowerShell\Modules\AzureAD\2.0.2.140\Microsoft.IdentityModel.Clients.ActiveDirectory.dll'
$authContext45 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList " https://login.windows.net/mytenant.onmicrosoft.com"
$secret = Get-ChildItem cert://localmachine/my/thumbprint
$CAC = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate]::new(appId,$secret)
$authenticationResult = $authContext45.AcquireTokenAsync("https://outlook.office365.com",$CAC)
$token = $authenticationResult.Result.AccessToken
$Authorization = "Bearer {0}" -f $Token
$Password = ConvertTo-SecureString -AsPlainText $Authorization -Force
$Ctoken = New-Object System.Management.Automation.PSCredential -ArgumentList "OAuthUser@tenantGUID",$Password
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true -Credential $Ctoken -Authentication Basic -AllowRedirection -Verbose
Import-PSSession $Session
However, when I try to use C# WSManConnectionInfo to do the same thing, I get this strange error whenever I try to open the runspace:
System.Management.Automation.Remoting.PSRemotingTransportException HResult=0x80131501 Message=Connecting to remote server outlook.office365.com failed with the following error message : The WS-Management service cannot process the request. Cannot find the https://schemas.microsoft.com/powershell/Microsoft.Exchange session configuration in the WSMan: drive on the outlook.office365.com computer. For more information, see the about_Remote_Troubleshooting Help topic. Here is the code:
public static Collection<PSObject> GetUsersUsingOAuthPublic()
{
var authContext = new AuthenticationContext("https://login.windows.net/mytenant.onmicrosoft.com");
X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
certStore.Close();
var cac = new ClientAssertionCertificate(appId, certCollection[0]);
var authResult = authContext.AcquireTokenAsync("https://outlook.office365.com", cac);
var token = authResult.Result.AccessToken;
string auth = string.Format("Bearer {0}", token);
System.Security.SecureString password = new System.Security.SecureString();
foreach (char c in auth)
{
password.AppendChar(c);
}
PSCredential psCredential = new PSCredential(string.Format("OAuthUser@{0}", tenantId), password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(
new Uri("https://outlook.office365.com/powershell-liveid?BasicAuthToOAuthConversion=true"),
"https://schemas.microsoft.com/powershell/Microsoft.Exchange",
psCredential
);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
return GetUserInformation(10, runspace);
}
}
I open the runspace like so:
public static Collection<PSObject> GetUserInformation(int count, Runspace runspace)
{
using (PowerShell powershell = PowerShell.Create())
{
powershell.AddCommand("Get-Users");
powershell.AddParameter("ResultSize", count);
runspace.Open();
powershell.Runspace = runspace;
return powershell.Invoke();
}
}
The exception: Image of exception
We've been working on this issue as well and we were able to get it to work. In this test case we used a secret instead of a certificate for the accessToken, but that shouldn't matter.
First difference is the uri should look like this:
Uri psURI = new($"https://outlook.office365.com/powershell-liveid?BasicAuthToOAuthConversion=true&email=SystemMailbox%7bbb558c35-97f1-4cb9-8ff7-d53741dc928c%7d%40{msDomain}");
The second change is the schema, with this change, you don't pass the full url for the schema, only the last part:
WSManConnectionInfo connectionInfo = new(psURI, "Microsoft.Exchange", psCredential)
{
AuthenticationMechanism = AuthenticationMechanism.Basic,
SkipCACheck = true,
SkipCNCheck = true
};
Instead of passing in "https://schemas.microsoft.com/powershell/Microsoft.Exchange" just pass in "Microsoft.Exchange"
Here is the full code that allowed us to run Get-Mailbox against a tenant with basic auth disabled:
string clientId = "";
string tenantId = "";
string secret = "";
string upn = "[email protected]";
string msDomain = "YOURDOMAIN.onmicrosoft.com";
IConfidentialClientApplication thisApp = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(secret)
.WithAuthority($"https://login.windows.net/{msDomain}/")
.Build();
AuthenticationResult accessToken = await thisApp.AcquireTokenForClient(new[] { $"https://outlook.office365.com/.default" }).ExecuteAsync();
//Bearer Header
string auth = $"Bearer {accessToken.AccessToken}";
SecureString password = GetSecureString(auth);
PSCredential psCredential = new($"OAuthUser@{tenantId}", password);
Uri psURI = new("https://outlook.office365.com/powershell-liveid?BasicAuthToOAuthConversion=true&email=SystemMailbox%7bbb558c35-97f1-4cb9-8ff7-d53741dc928c%7d%40{domain}");
WSManConnectionInfo connectionInfo = new(psURI, "Microsoft.Exchange", psCredential)
{
AuthenticationMechanism = AuthenticationMechanism.Basic,
SkipCACheck = true,
SkipCNCheck = true,
};
using Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
using PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddCommand("Get-Mailbox");
ps.AddParameter("Identity", upn);
var results = await ps.InvokeAsync();