0

The LoadFile() function is really giving me a tough time. I have two assemblies, Asm1.dll, and Asm2.dll that are being installed in the GAC. Later, these assemblies are loaded using LoadFile() to create a matching type library.

This all works well for Asm1.dll (tested in PowerShell):

[System.Reflection.Assembly]::LoadFile("D:\MyTools\Asm1.dll")

GAC    Version        Location
True   v4.0.30319     C:\Windows\Microsoft.Net\assembly\GAC_64\Asm1\v4.0_4.0.30000.0__abcdef123456\Asm1.dll

but it does not for Asm2.dll

[System.Reflection.Assembly]::LoadFile("D:\MyTools\Asm2.dll")

Exception calling "LoadFile" with "1" argument(s): "Could not load file or assembly 'Asm2.dll'
or one of its dependencies. The specified module could not be found."

However, both assemblies do exist in my work folder as well as in the GAC:

gci C:\Windows\Microsoft.Net\assembly\GAC_64\Asm2\v4.0_2.3.30000.0__abcdef123456\Asm2.dll
gci C:\Windows\Microsoft.Net\assembly\GAC_64\Asm1\v4.0_4.0.30000.0__abcdef123456\Asm1.dll

Mode                LastWriteTime         Length Name
-a----      10 Mar 2021     15:37        1494528 Asm2.dll
-a----      10 Mar 2021     12:47        6749184 Asm1.dll

gci D:\MyTools\Asm1.dll
gci D:\MyTools\Asm2.dll

Mode                LastWriteTime         Length Name
-a----      10 Mar 2021     12:47        6749184 Asm1.dll
-a----      10 Mar 2021     14:00        1494528 Asm2.dll

I also tried LoadFile() with the full GAC path - with the same results for both assemblies.

From the past I know that after scrubbing the HKCU registry and GAC from all traces of my components before rebuilding my tools, the problem disappears. But it did resurface this morning after I had updated Visual Studio and rebuilt my tools without first scrubbing everything clean.

The LoadFile() function acts like a black box, giving me exactly zero information about what went wrong. I don't want to work around the problem anymore by "demolishing my entire building when I'm only looking for a blown fuse", figuratively speaking.

So how can I track the root cause of this failure?

Edit

@Efie Thank you. The $error[0] only points to the LoadFile() function as the error source. No additional insights there.

@Ian Kemp, @Jeroen Mostert Thank you for recommending the fusion log viewer.

It does provide some information but still does not indicate the actual cause. On the contrary - the log indicates that assembly binding succeeded while the error message still appears in the PowerShell console.

The operation was successful.

=== Pre-bind state information ===
LOG: DisplayName = Asm2, Version=2.3.30000.0, Culture=neutral, 
PublicKeyToken=abcdef123456
 (Fully-specified)
LOG: Appbase = file:///C:/Windows/system32/WindowsPowerShell/v1.0/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = PowerShell_ISE.exe
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: 
C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell_ISE.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from 
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Binding succeeds. Returns assembly from C:\Windows\Microsoft.Net\assembly\GAC_64\Asm2\v4.0_2.3.30000.0__abcdef123456\Asm2.dll.
LOG: Assembly is loaded in default load context.

The log files for both assemblies Asm1 and Asm2 are identical apart from the names, versions and the fact that fusion log explicitly reported it found Asm1 in the GAC whereas it didn't do so for Asm2:

LOG: Post-policy reference: Asm1, Version=4.0.30000.0, Culture=neutral, PublicKeyToken=abcdef123456
LOG: Found assembly by looking in the GAC.

The log files for both assemblies Asm1 and Asm2 are identical apart from the names and versions if I specify the complete GAC path in both calls to LoadFile(); including the Post-policy-line.

Frank Heimes
  • 100
  • 6
  • There *may* be additional information inside the error object. Only the message gets printed to the screen. You can inspect recent errors by inspecting the ```$error``` variable, like ```$error[0]``` for example. Sometimes there is additional info hidden in there. ```$error[0] | Get-Member``` will get you a list of properties which you can dig into further. – Efie Mar 10 '21 at 15:39
  • That black box does have a logging facility. [See also](https://stackoverflow.com/q/255669/4137916). – Jeroen Mostert Mar 10 '21 at 15:42
  • Does this answer your question? [How to enable assembly bind failure logging (Fusion) in .NET](https://stackoverflow.com/questions/255669/how-to-enable-assembly-bind-failure-logging-fusion-in-net) – Ian Kemp Mar 10 '21 at 15:59
  • I'm calling out those who downvoted this question to present a solution! In the mean time I ran dependency walker. It detected a dependency of Asm1.dll to `OLE32.DLL` (unsurprisingly). But it also detected a circular dependency between `c:\windows\system32\OLE32.DLL` and `c:\program files\powershell\7\API-MS-WIN-CORE-COM-L1-1-0.DLL` which I consider a bug in PowerShell. However, temporarily renaming the PowerShell folder removed the circular dependency but did not fix the original problem. Any more ideas? – Frank Heimes Mar 17 '21 at 15:03
  • Any solution, I have similar issue? – Zoli Jun 01 '22 at 13:25

1 Answers1

1

A pattern I've used in the past is something like this (I'm doing it from memory so might need a tweak to actually work!):

try
{
    Add-Type -LiteralPath "c:\temp\myassembly.dll";
}
catch [System.Reflection.ReflectionTypeLoadException]
{
    $ex = $_.psbase.Exception;
    foreach( $item in $ex.LoaderExceptions )
    {
        write-host $ex.ToString();
    }
    throw;
}
mclayton
  • 8,025
  • 2
  • 21
  • 26
  • That returns the exact same information as the exception printed by PowerShell when calling `[System.Reflection.Assembly]::LoadFile("c:\temp\myassembly.dll")`. – Frank Heimes Mar 11 '21 at 08:58