Could not load file or assembly

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:

Similar results if I login to the HRW and Import-Module Az followed by Connect-ExchangeOnline:

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:

That didn’t work for me though, I kept getting the same error.

I learnt of this cmdlet to identify the loaded assemblies though:

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:

Or even:

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:

Failed.

Same if I remove the Az modules and also ExchangeOnlineManagement.

What if I load the ExchangeOnlineManagement module first?

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?

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.