SOLVED: Exchange Online Management PowerShell Connect-ExchangeOnline bug "A window handle must be configured. See https://aka.ms/msal-net-wam#parent-window-handles"
While you're here why not check out our Exchange audit and documentation tool?
If you're using the Exchange Online Management PowerShell cmdlets you may notice that the following error:
A window handle must be configured. See https://aka.ms/msal-net-wam#parent-window-handles
This error appears in version 3.7.0 (and persists in version 3.7.1) when the following change was made.
Integrated WAM (Web Account Manager) in Authentication flows to enhance security.
You notice that this error occurs in PowerShell ISE but not in a PowerShell, the issue also appears in any Windows based applications - for example a WinForms application that tries to execute Connect-ExchangeOnline
![]() |
PowerShell ISE |
![]() |
.NET console application |
While you're here why not check out our Exchange audit and documentation tool?
The error occurs because Microsoft has made the (correct) decision that when an interactive login prompt it must have a parent window so that the dialog doesn't appear behind another window thereby not visible to the user. This is implemented by the MSAL library.
However the code they used to determine the parent window can be seen below.
/// <summary>
/// Initializes the Public Client Application or extract from map
/// if already present. Also it enters the instance in the map if
newly created.
/// </summary>
/// <returns>An instance of PCA from the map or intializes and returns it</returns>
private IPublicClientApplication GetPublicClientInstance()
{
IPublicClientApplication
publicClientApplication;
if (!MSALTokenProvider.publicClientApplicationMap.TryGetValue(this.context.AzureADAuthorizationEndpointUri,
out
publicClientApplication))
{
BrokerOptions brokerOption = new
BrokerOptions(BrokerOptions.OperatingSystems.Windows);
publicClientApplication =
(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
PublicClientApplicationBuilder.Create(this.context.ClientAppId).WithAuthority(this.context.AzureADAuthorizationEndpointUri,
true).WithRedirectUri(this.context.ClientAppRedirectUri.ToString()).WithClientCapabilities(new string[] { "cp1" }).Build() :
PublicClientApplicationBuilder.Create(this.context.ClientAppId).WithAuthority(this.context.AzureADAuthorizationEndpointUri,
true).WithRedirectUri(this.context.ClientAppRedirectUri.ToString()).WithClientCapabilities(new string[] { "cp1"
}).WithParentActivityOrWindow(new
Func<IntPtr>(ParentWindowHandle.GetConsoleOrTerminalWindow)).WithBroker(brokerOption).Build());MSALTokenProvider.publicClientApplicationMap.TryAdd(this.context.AzureADAuthorizationEndpointUri,
publicClientApplication);
}
return publicClientApplication;
}
As seen in the code above this uses the GetConsoleWindow low level API to determine the handle of the parent console window.
https://learn.microsoft.com/windows/console/getconsolewindow
"We do not recommend using this content in new products"
They are using dated methods that they don't recommend themselves, but more importantly they are looking for a console window and therefore if you're not running a console app then there's no handle returned from the method.
The issue persists in ExchangeOnlineManagement version 3.7.1 so it's a concern that Microsoft are not going to fix this issue.
Workaround 1: Downgrade
In the mean time you can uninstall the latest versions and roll-back to version 3.6.0 which didn't have the issue.
Uninstall-Module -Name ExchangeOnlineManagement -AllVersions -Force
Install-Module -Name ExchangeOnlineManagement -RequiredVersion 3.6.0 -Force
Workaround 2: Open a console window
You can also open a console window in PowerShell ISE or your windows application, this is not great as it opens a console window in the background.
$consoleSupportSource = @’
using
System;
using
System.Runtime.InteropServices;
public
class ConsoleSupportMethods
{
[DllImport("kernel32.dll",
SetLastError = true)]
public static extern int AllocConsole();
[DllImport("kernel32.dll",
SetLastError = true)]
public static extern int FreeConsole();
}
‘@
Add-Type -TypeDefinition $consoleSupportSource
try
{
[ConsoleSupportMethods]::AllocConsole();
Connect-ExchangeOnline;
}
catch
{
throw;
}
finally
{
[ConsoleSupportMethods]::FreeConsole();
}
Workaround 3: Handle MSAL Authentication yourself
$msalPath = [System.IO.Path]::GetDirectoryName((Get-Module ExchangeOnlineManagement).Path);
Add-Type -Path "$msalPath\Microsoft.IdentityModel.Abstractions.dll";
Add-Type -Path "$msalPath\Microsoft.Identity.Client.dll";
[Microsoft.Identity.Client.IPublicClientApplication] $application = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create("fb78d390-0c51-40cd-8e17-fdbfab77341b").WithDefaultRedirectUri().Build();
$result = $application.AcquireTokenInteractive([string[]]"https://outlook.office365.com/.default").ExecuteAsync().Result;
Connect-ExchangeOnline -AccessToken $result.AccessToken
-UserPrincipalName $result.Account.Username;
- Get-Module needs a -ListAvailable (or an Import-Module ExchangeOnlineManagement), or you get an error when you haven't loaded the module yet
ReplyDelete- How do you log in using a different accountname? When I give a different accountname, I get the MFA popup for my own windows account.
- And this works until Entra feels that you need to verify your MFA data, then you get a 'your browser is not supported' error.
Hello, yes you should probably import module first but I found on my system I didn't have to.
Delete- How do you log in using a different accountname? When I give a different accountname, I get the MFA popup for my own windows account.
Where do you mean? In the MSAL code you can provide a login hint so that the system tries to automatically login with a specific account - do you mean that? Or that the system tries to log you in with a specific account?
Sometimes you get locked into an account with the Microsoft login prompt but you can often click Back to get to a list of accounts.
Login hints and prompts also affect this.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.client.acquiretokeninteractiveparameterbuilder.withloginhint?view=msal-dotnet-latest
https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.client.acquiretokeninteractiveparameterbuilder.withprompt?view=msal-dotnet-latest
We use this code
Prompt prompt = !String.IsNullOrEmpty(LoginHint) ? Prompt.NoPrompt : Prompt.SelectAccount;
AuthenticationResult result = Task.Run(async () => await application.AcquireTokenInteractive(scopes).WithPrompt(prompt).WithLoginHint(LoginHint).WithExtraScopesToConsent(extraScopes).ExecuteAsync().ConfigureAwait(false)).GetAwaiter().GetResult();