Provided that there is a dotnet core sdk istalled, run script as below.
What script does:
netstandard2.0)Hello from the dll and expects to receive predefined output (== "Works!")Function ShowErrorDetails
{
param(
$ErrorRecord = $Error[0]
)
$out=""
$out+= $($ErrorRecord | Format-List -Property * -Force | out-string)
$out+= $($ErrorRecord.InvocationInfo | Format-List -Property * | out-string)
$Exception = $ErrorRecord.Exception
for ($depth = 0; $Exception -ne $null; $depth++)
{
$out+= $("$depth" * 80)
$out+= $($Exception | Format-List -Property * -Force | out-string)
$Exception = $Exception.InnerException
}
# emit return object
$out
}
$myLibName="MyLib"
$myLibDllName="${myLibName}.dll"
$csProjName = "${myLibName}.csproj"
$thisDir = Get-Location
$workdir = Join-Path $thisDir "workdir"
$libraryCodeDir = Join-Path $workdir $myLibName
$csProjFilePath = Join-Path $libraryCodeDir $csProjName
$publishedOutputDir = Join-Path $workdir "published"
$dllFilePath = Join-Path $publishedOutputDir $myLibDllName
Write-Host "thisDir : '$thisDir'"
Write-Host "myLibName : '$myLibName'"
Write-Host "csProjName : '$csProjName'"
Write-Host "workdir : '$workdir'"
Write-Host "libraryCodeDir : '$libraryCodeDir'"
Write-Host "csProjFilePath : '$csProjFilePath'"
Write-Host "publishedOutputDir : '$publishedOutputDir'"
Write-Host "myLibDllName : '$myLibDllName'"
Write-Host "dllFilePath : '$dllFilePath'"
Write-host "------ >>>>> Powershell version : "
$PsVersionTable | Out-Host
Write-Host "------ >>>>> Dotnet core version: "
dotnet --info
Write-host "------ >>>>> Creating $libraryCodeDir..."
New-item $libraryCodeDir -ItemType Directory -Force
Write-Host "`n------ >>>>> Creating library template in '$libraryCodeDir'"
dotnet new classlib --name $myLibName --output $libraryCodeDir --framework "netstandard2.0" --force
$expectedOutput = "Works!"
$codeContent = @"
public static class MyClass
{
public static string Hello()
{
return "${expectedOutput}";
}
}
"@
try
{
Set-Content -Path $libraryCodeDir/Class1.cs -Value $codeContent
dotnet publish -c Debug -o ${publishedOutputDir} ${csProjFilePath}
# if compilation was not successful
if($global:LASTEXITCODE -ne 0 )
{
Write-Error "------ >>>>> Compilation failed, cannot continue with the script.."
Exit 1
}
else
{
Write-Host "------ >>>>> OK: compilation successful"
}
Write-Host "------ >>>>> Loading library..."
$bytes = [System.IO.File]::ReadAllBytes($dllFilePath)
$loadedLib = [System.Reflection.Assembly]::Load($bytes)
$loadedLib.GetTypes() | Out-Host
$functionCallResult = [MyClass]::Hello()
if($functionCallResult -eq $expectedOutput )
{
Write-Host "------ >>>>> SUCCESS: Function call returned expected result"
}
else
{
Write-Error "------ >>>>> FAILURE: Function call unxpected result '${functionCallResult}' while expecting '${expectedOutput}'"
Exit 1
}
}
catch {
ShowErrorDetails | Out-Host
}
After the script finishes , user should see line like
------ >>>>> SUCCESS: Function call returned expected result
I've added a debug function ShowErrorDetails to show full error details (code taken from book Windows Powershell in action 3rd edition) if something goes wrong.
Powershell 7.0 cannot find the type from the library and at the end of error output it prints something like:
00000000000000000000000000000000000000000000000000000000000000000000000000000000
ErrorRecord : Unable to find type [MyClass].
WasThrownFromThrowStatement : False
TargetSite : Void CheckActionPreference(System.Management.Automation.Language.FunctionContext, System.Exception)
StackTrace : at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
Message : Unable to find type [MyClass].
Data : {System.Management.Automation.Interpreter.InterpretedFrameInfo}
InnerException :
HelpLink :
Source : System.Management.Automation
HResult : -2146233087
The issue is related only for powershell 7:
I tested it on :
Windows
Name Value
---- -----
PSVersion 7.0.0
PSEdition Core
GitCommitId 7.0.0
OS Microsoft Windows 10.0.17763
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Ubuntu:
Name Value
---- -----
PSVersion 7.0.0
PSEdition Core
GitCommitId 7.0.0
OS Linux 5.3.0-40-generic #32~18.04.1-Ubuntu SMP Mon Feb 3 14:05:59 UTC 2020
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
I have verified that this correctly WORKS platforms such as
windows powershell 5.1
Name Value
---- -----
PSVersion 5.1.17763.1007
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.17763.1007
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
windows powershell core 6.2.4
Name Value
---- -----
PSVersion 6.2.4
PSEdition Core
GitCommitId 6.2.4
OS Microsoft Windows 10.0.17763
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
ubuntu powrshell core 6.2.4
Name Value
---- -----
PSVersion 6.2.4
PSEdition Core
GitCommitId 6.2.4
OS Linux 5.3.0-40-generic #32~18.04.1-Ubuntu SMP Mon Feb 3 14:05:59 UTC 2020
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
ubuntu_pwsh6.2.4_output.txt
ubuntu_pwsh7_output.txt
windows_powershell_output.txt
windows_pwsh6.2.4_output.txt
windows_pwsh7_output.txt
Looks like Assembly.Load doesn't load into the default assembly load context (which is where PowerShell now looks for type resolution).
Here's a work around:
$bytes = [System.IO.File]::ReadAllBytes($dllFilePath)
$loadedLib = [Runtime.Loader.AssemblyLoadContext]::Default.LoadFromStream(
[IO.MemoryStream]::new($bytes))
@SeeminglyScience Did that changed between pwsh 6.x and 7.x ?
Yeah, it changed in #11088
@daxian-dbw Maybe resolution could look at AssemblyLoadContext.All and either pick out the ALCs with the name "Assembly.Load(byte[], ...)" or that are of the (non-public) type System.Runtime.Loader.IndividualAssemblyLoadContext?
We discussed this in #11088 and intermediate conclusion was that it is by-design. Really there are two methods which load in non default context. .Net Core implements in such manner and it is not clear why we should change this and de-facto revert #11088.
.Net Core implements in such manner and it is not clear why we should change this
So that the types can be resolved by PowerShell, which would avoid making Assembly.Load essentially impossible to use from PowerShell (and breaking all code that uses it).
and de-facto revert #11088.
The way I proposed would still filter out assemblies explicitly loaded in custom assembly load contexts. Nothing would need to be reverted.
It is .Net Core behaviour for ALC.
I think #11088 is right direction to switch to ALC from AppDomain since we are discussing (1) support dll-s with different versions, (2) resolving name conflicts from different dll-s, (3) dll unloading.
It is .Net Core behaviour for ALC.
I think #11088 is right direction to switch to ALC from AppDomain since we are discussing (1) support dll-s with different versions, (2) resolving name conflicts from different dll-s, (3) dll unloading.
I agree. That's why I proposed a solution that doesn't interfere with that.
I updated my PowerShell from 6 to 7 a few days ago, and found out that a lot of my scripts have stopped working.
Is there a workaround? Can I use some other method than Assembly.Load, so that PowerShell finds the types? (I use the overload getting a byte array, so that the .dll file isn't locked and can be deleted/overwritten.)
Few answers above: https://github.com/PowerShell/PowerShell/issues/12052#issuecomment-595810608
Sorry that I missed this one for so long :(
To be honest, I didn't realize Assembly.Load(byte[]) loads the assembly to an anonymous load context. I did know Assembly.LoadFile does that, but thought it was fine to not discover it because Assembly.LoadFile is useful only when you are trying to load a conflict assembly file and then do pure reflection on the returned assembly.
I think @SeeminglyScience's proposal makes sense. Let me look into this more and reply back.
There was another issue where we discussed this that I'd like to link to update the original opener, but I can't find it. Let me know if you remember it
That conversation was in #11088 which introduced this regression. I already notified @stevenebutler who reported it.
:tada:This issue was addressed in #12203, which has now been successfully released as v7.1.0-preview.2.:tada:
Handy links:
:tada:This issue was addressed in #12203, which has now been successfully released as v7.0.1.:tada:
Handy links:
Most helpful comment
Sorry that I missed this one for so long :(
To be honest, I didn't realize
Assembly.Load(byte[])loads the assembly to an anonymous load context. I did knowAssembly.LoadFiledoes that, but thought it was fine to not discover it becauseAssembly.LoadFileis useful only when you are trying to load a conflict assembly file and then do pure reflection on the returned assembly.I think @SeeminglyScience's proposal makes sense. Let me look into this more and reply back.