Powershell: Can't use add-type / "using assembly" to get types from local project assembly

Created on 1 May 2020  路  15Comments  路  Source: PowerShell/PowerShell

I have a local C# solution (clone of https://github.com/markm77/open-banking-connector-csharp) and I can't seem to be able to load the types of one of its projects. I am developing a PowerScript module to interface with the C# but and really need to have access to the types for cmdlet input/output.

Steps to reproduce

I re-build the C# solution, then run the following in PowerShelll:

add-type -assemblyname C:\Repos\GitHub\markm77\open-banking-connector-csharp\src\OpenBanking.Library.Connector\bin\Debug\netstandard2.1\FinnovationLabs.OpenBanking.Library.Connector.dll -passthru

Expected behavior

Types are loaded with no errors.

Actual behavior

The following errors are produced:

Add-Type: Unable to load one or more of the requested types.
Could not load file or assembly 'Microsoft.EntityFrameworkCore, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.
Could not load file or assembly 'Microsoft.EntityFrameworkCore, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'FluentValidation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7de548da2fbae0f0'. The system cannot find the file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'AutoMapper, Version=9.0.0.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005'. The system cannot find the 
file specified.
Could not load file or assembly 'Microsoft.EntityFrameworkCore, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.

Environment data

ame                           Value
----                           -----
PSVersion                      7.0.0
PSEdition                      Core
GitCommitId                    7.0.0
OS                             Microsoft Windows 10.0.18363
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Question Resolution-Answered

Most helpful comment

@markm77 when you just do dotnet build, the dependency assemblies aren't copied. PowerShell doesn't look at the nuget cache, you need to do a full dotnet publish and import from the $Configuration\$Framework\publish directory.

All 15 comments

Can you try with Add-Type -Path rather than Add-Type -AssemblyName @markm77 ?

Thanks for the replies but yes I had already tried this with same result.

@markm77 when you just do dotnet build, the dependency assemblies aren't copied. PowerShell doesn't look at the nuget cache, you need to do a full dotnet publish and import from the $Configuration\$Framework\publish directory.

Edit: to help anyone else reading this, the other way to get the dependency assemblies is to add <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> to the C# project file.


Thanks @SeeminglyScience , great tip! That solved the errors. I believe the types are loaded now but wondering how to get a full list at any time to check? Get-TypeData seems to ignore types loaded from assemblies..... Also why do you guys recommend Add-Type -Path over Add-Type -AssemblyName as I couldn't see any differences?

PS I will close this issue shortly as believe solved.

Thanks @SeeminglyScience , great tip! That solved the errors. I believe the types are loaded now but wondering how to get a full list at any time to check?

[Runtime.Loader.AssemblyLoadContext]::Default.Assemblies.
    Where{ $_.Location -like "$publishDirectory*" }.
    ForEach('GetTypes').
    Where{ $_.IsPublic }

Or

Install-Module ClassExplorer
Find-Type
Get-Assembly | ? Location -like "$publishDirectory" | Find-Type
Get-Assembly OpenBanking* | Find-Type
Find-Type -Namespace *OpenBanking*

Get-TypeData seems to ignore types loaded from assemblies.....

Yeah that's just for PowerShell's extended type system. You would use that to get information about loaded .types.ps1xml's for instance.

Also why do you guys recommend Add-Type -Path over Add-Type -AssemblyName as I couldn't see any differences?

Assembly name is meant for things like

System.Management.Automation, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
System.Management.Automation

It may work if you give it a path, but I wouldn't rely on that. That seems like an accidental implementation detail, I'm not sure if it would be considered part of the change contact. Either way Path is more explicit in intent.

Thanks again @SeeminglyScience . So I ran the following script to look at loaded types, either from Add-Type or using assembly:

start-transcript -path .\outputFileName.txt
[Runtime.Loader.AssemblyLoadContext]::Default.Assemblies.
Where{ $_.Location -like "$publishDir*" }.
ForEach{ $_.GetTypes().
  Where{ $_.IsPublic }
} | Format-Table -Property IsPublic, Name, Namespace
stop-transcript

and what I noticed was quite weird.

For using assembly when running the first time the dependent types were missing, the second time a block of them were added and the third time a much smaller final block was added. I tried waiting a while after issuing using assembly but no you really do need to run the script 3 times.

For Add-Type when running the first time the first two blocks mentioned above were present, and the second time the final block from above was added.

So I shall always run this command 3 times in future (very strange) to see all loaded types.

I also ran the above command with a C# PS module project after loading the dot-published DLL with using module and using assembly. There using module seemed to pull in all dependencies but using assembly pulled in none, not even those which were used by a PSCmdlet. Since this is my actual use case, a cmdlet with .NET input and output types, seems like I simply need to prefix client PS scripts with using module.

Any thoughts appreciated even though I'll close this now.

When you load an assembly dependencies are typically loaded when they're needed. Often this is when a method is first jitted. The only way to ensure that dependency assemblies are loaded before the dependent needs them is to import them yourself.

using module will incidentally end up loading more because your assembly is crawled by PowerShell looking for cmdlets. During that, some methods may be jitted, some static constructors may be called, but I wouldn't depend on that.

Thanks @SeeminglyScience , I managed to get the same result from using module (3 runs) and using assembly (4 runs) by adding a non-PSCmdlet class with a dependency. So seems using assembly ignores PSCmdlet dependencies which is no good if these are the only dependencies and we want to expose .NET types related to these....

I proved this by dot-publishing the PS binary module with only the PSCmdlet class (with a .NET class input coming from a dependency) and then trying to create an input (.NET class) for the cmdlet:

[PSCmdletInputType]::new()

This succeeded with using module but not using assembly (error: InvalidOperation: Unable to find type [PSCmdletInputType].

Not sure if this is a bug or design behaviour.

using assembly doesn't do anything at parse time (never implemented, but also quite difficult to solve), which I'm guessing is where you're getting those errors (this for PS classes yeah?). The only way to guarantee that an assembly will be available is to load it before parse time. Everything else that works incidentally, I wouldn't recommend depending on.

Are you saying it's not safe to do the following with .NET types TypeA and TypeB?

using module ./x.dll # where x.dll depends on y.dll which contains TypeA, TypeB
$a = [TypeA]::new()
$b = MyCmdletFromBinaryModuleX $a # $b is of type TypeB

Yeah. Even if it happens to work, you would be depending on implementation details that are subject to change.

Edit: I now believe below is actually how things should behave rather than being a workaround as it's like in C# needing references to two packages one of which depends on the other....


I believe you are right as now I have moved from a simple example to more realistic code the dependency assemblies are no longer loaded by using module and code above fails due to unknown type.

Is the workaround below safe where we force the relevant dependency to load via using assembly? It works in my testing.

I don't really know what parse-time means but assume this is the first pass of the code by the interpreter?

using module ./x.dll # where x.dll depends on y.dll which contains TypeA, TypeB
using assembly ./y.dll # <-- WORKAROUND
$a = [TypeA]::new()
$b = MyCmdletFromBinaryModuleX $a # $b is of type TypeB

Sorry @SeeminglyScience for spamming you with so many questions! I believe actually the above code is okay as the parse-time issue relates to creation of PS types: https://github.com/PowerShell/PowerShell/issues/3641.

Enjoy the rest of your weekend.

In cases anyone is interested, in the end I decided it was user-unfriendly and fiddly to get users to create .NET types so I've created New cmdlets for their convenience. I can also now use the more flexible Import-Module command instead of using. So the example above becomes

Import-Module ./x.dll # where x.dll depends on y.dll which contains TypeA, TypeB
$a = New-TypeA
$b = MyCmdletFromBinaryModuleX $a # $b is of type TypeB

Everything works so much better!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andschwa picture andschwa  路  3Comments

Michal-Ziemba picture Michal-Ziemba  路  3Comments

rudolfvesely picture rudolfvesely  路  3Comments

rkeithhill picture rkeithhill  路  3Comments

JohnLBevan picture JohnLBevan  路  3Comments