Continuing with my efforts to move all my Azure Automation Runbooks to PowerShell 7.2, yesterday I decided to tackle a couple of Runbooks that use the ExchangeOnlineManagement
module.
I installed PowerShell 7.2 on the Hybrid Runbook Worker (HRW) and installed the latest version of the ExchangeOnlineManagement
and Az
modules on it (this is side by side with the existing version in PowerShell 5.x as I detailed previously; long story short, install it via Install-Module
but with the -Force
switch).
Running Connect-ExchangeOnline
by itself worked fine in the Runbook, but if I use any Az
cmdlet first (because I need to access the Key Vault etc.) then Connect-ExchangeOnline
complains:
1 |
Could not load file or assembly 'Microsoft.Identity.Client, Version=4.41.0.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae |
Similar results if I login to the HRW and Import-Module Az
followed by Connect-ExchangeOnline
:
1 2 |
OperationStopped: Could not load file or assembly 'Microsoft.IdentityModel.Tokens, Version=6.22.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. Could not find or load a specific file. (0x80131621) |
Slightly different assembly this time, but same stuff I guess.
Initially this forum post seemed like it should do the trick. Apparently I should in an admin PowerShell 7 window (something I missed initially) do the following:
1 2 |
# Replace the version with whatever the error message shows Install-Module -Name Microsoft.Identity.Client -RequiredVersion 4.41.0.0 |
That didn’t work for me though, I kept getting the same error.
I learnt of this cmdlet to identify the loaded assemblies though:
1 |
[System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location | Sort-Object -Property FullName | Select-Object -Property FullName, Location, GlobalAssemblyCache, IsFullyTrusted | Out-GridView |
This showed me that with the Microsoft.Identity.Client
assembly at least the file isn’t loaded. But this is probably a red-herring coz maybe ExchangeOnlineManagement
is failing before it reaches this stage, when I am trying this manually.
The other assembly is present though.
And as you can see it’s present both from the Az
module, as well as the ExchangeOnlineManagement
module – which is the cause of this conflict.
Turns out you can load assemblies manually, like this for instance:
1 |
Add-Type -Path 'C:\Program Files\PowerShell\Modules\ExchangeOnlineManagement\3.3.0\netCore\System.IdentityModel.Tokens.Jwt.dll' |
Or even:
1 |
Add-Type -AssemblyName 'Microsoft.IdentityModel.Tokens, Version=6.22.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' |
I tried that, but it didn’t help. ExchangeOnlineManagement
continued complaining.
Hmm.
What exactly is an assembly? From this post:
An assembly is a packaged chunk of functionality (the .NET equivalent of a DLL). Almost universally an assembly will consist of exactly one file (either a DLL or an EXE). To make things confusing, the naming convention for assemblies is very similar to the naming convention for namespaces. Be warned: they are not the same thing! An assembly may contain classes from many namespaces, and a namespace may cover many assemblies. Although not strictly correct, you can think of the assembly as the physical file containing the executable code and a namespace as a category to organize all the code that relates to a particular area.
Namespaces are just labels that are used to allow classes from different assemblies to have the same name and not interfere with each other. Much like classes form a container, or boundary, for their members (so many different classes can have an “Open()” method, for example), namespaces form a container for classes with the same name. Native .NET namespaces start with “System” (this is the root of the .NET namespace universe). The convention for application-specific namespaces is to start them with {CompanyName}.{ProductName}. So, for example, you may see Microsoft.Office.{some more stuff} for Office-related classes.
Ok, so we are back to the DLL hell days. 🙂
I then found this good article about assemblies and conflicts.
In .NET, dependency conflicts occur when two versions of the same assembly are loaded into the same Assembly Load Context. This term means slightly different things on different .NET platforms, which is covered later in this article. This conflict is a common problem that occurs in any software where versioned dependencies are used.
Conflict issues are compounded by the fact that a project almost never deliberately or directly depends on two versions of the same dependency. Instead, the project has two or more dependencies that each require a different version of the same dependency.
Interestingly, turns out PowerShell’s own dependencies can conflict with the dependencies of its modules. I have encountered this in the past, for the specific example given in that post, but hadn’t realized. That article has some good suggestions too on what to do, but none of them apply to my situation of using HRWs… I think.
While all this was good info, eventually I was still stuck without a real solution.
I tried to remove all the Az
modules after they had done their work, and then run Connect-ExchangeOnline:
1 2 |
Get-Module | Where-Object { $_.Name -match "^Az" } | Remove-Module Connect-ExchangeOnline @connectParams |
Failed.
Same if I remove the Az
modules and also ExchangeOnlineManagement
.
What if I load the ExchangeOnlineManagement
module first?
1 2 3 4 5 6 7 |
Import-Module ExchangeOnlineManagement Import-Module Az # Do the Azure stuff... # Then connect to ExO Connect-ExchangeOnline @connectParams |
I have to load the Az
module before connecting, coz that’s how I get my certs from the Key Vault.
Nope, doesn’t work!
Any difference if I load the specific ones I need?
1 2 3 4 5 6 7 8 |
Import-Module ExchangeOnlineManagement Import-Module Az.Accounts Import-Module Az.KeyVault # Do the Azure stuff... # Then connect to ExO Connect-ExchangeOnline @connectParams |
No way, that worked!!
But that was when I tested on the HRW directly. What if I do this in the Runbook? Does it work?
It actually did! Whee! 😱🥳
Weird thing is if I look at the loaded assemblies the version ExchangeOnlineManagement
wants isn’t even loaded:
Update: This also solves issues with PnP.PowerShell
. Even though I am not specifically loading it above, in my code I am doing Connect-PnPOnline
after Connect-ExchangeOnline
and it works fine.
Update (3rd Nov 2023): While reading Tony Redmond’s blog I see that he too encountered this last month. Like he said “…it’s disappointing that two Microsoft engineering groups working in the Microsoft 365 ecosystem cannot agree on which version of a critical DLL to use.” Disappointing indeed.