Background
Using visual studio 2019 v16.10.3 I have created this (standard MS example source) class library and successfully compiled into MyMathLib.DLL
using System;
namespace MyMathLib
{
public class Methods
{
public Methods()
{
}
public static int Sum(int a, int b)
{
return a + b;
}
public int Product(int a, int b)
{
return a * b;
}
}
}
My MyMathLib.csproj file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
And then I want to try executing this code using powershell using the following script:
[string]$assemblyPath='S:\Sources\ResearchHans\DotNetFromPowerShell\MyMathLib\MyMathLib\bin\Debug\net5.0\MyMathLib.dll'
Add-Type -Path $assemblyPath
When I run this I get an error, and ask what's going on by querying the error for LoaderExceptions:
PS C:\WINDOWS\system32> S:\Sources\ResearchHans\DotNetFromPowerShell\TestDirectCSharpScript2.ps1
Add-Type : Kan een of meer van de gevraagde typen niet laden. Haal de LoaderExceptions-eigenschap op voor meer informatie.
At S:\Sources\ResearchHans\DotNetFromPowerShell\TestDirectCSharpScript2.ps1:2 char:1
+ Add-Type -Path $assemblyPath
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Add-Type], ReflectionTypeLoadException
+ FullyQualifiedErrorId : System.Reflection.ReflectionTypeLoadException,Microsoft.PowerShell.Commands.AddTypeCommand
PS C:\WINDOWS\system32> $Error[0].Exception.LoaderExceptions
Kan bestand of assembly System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a of een van de afhankelijkhed
en hiervan niet laden. Het systeem kan het opgegeven bestand niet vinden.
PS C:\WINDOWS\system32>
(Sorry for dutch windows messages: something like "Cannot load one or more of the types" and the loader exception Cannot load file or assembly "System.Runtime" etc...
I have seen many related issues on SO, but they are usually quite old and do not refer to .NET5.0
This works
When I directly compile the example source like below, it works just fine.
$code = #"
using System;
namespace MyNameSpace
{
public class Responder
{
public static void StaticRespond()
{
Console.WriteLine("Static Response");
}
public void Respond()
{
Console.WriteLine("Instance Respond");
}
}
}
"#
# Check the type has not been previously added within the session, otherwise an exception is raised
if (-not ([System.Management.Automation.PSTypeName]'MyNameSpace.Responder').Type)
{
Add-Type -TypeDefinition $code -Language CSharp;
}
[MyNameSpace.Responder]::StaticRespond();
$instance = New-Object MyNameSpace.Responder;
$instance.Respond();
Why?
The whole Idea to have a proof of concept that I can utilize my .NET5 libraries using e.g. powershell scripts. I actually have to load a much more complicated assembly, but this oversimplified example seems to be my primary issue at the moment.
The question
What am I missing? - How do I get this to work?
TIA for bearing with me.
You didn't state what version of Powershell that you're using. However, I can reproduce the error by the following.
In VS 2019, create a Class Library (C# Windows Library).
Rename the class from "Class1.cs" to "Methods.cs"
Methods.cs
using System;
namespace MyMathLib
{
public class Methods
{
public int Sum(int a, int b)
{
return a + b;
}
public int Product(int a, int b)
{
return a * b;
}
}
}
Compile.
Open Windows Powershell
Type: (Get-Host).version
Then type the following:
PS C:\Users\Test> [string]$assemblyPath="C:\Temp\MyMathLib.dll"
PS C:\Users\Test> Add-Type -Path $assemblyPath
which results in the following error:
Add-Type : Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
At line:1 char:1
+ Add-Type -Path $assemblyPath
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Add-Type], ReflectionTypeLoadException
+ FullyQualifiedErrorId : System.Reflection.ReflectionTypeLoadException,Microsoft.PowerShell.Commands.AddTypeCommand
This occurs because .NETCore 5 isn't supported by Windows Powershell 5.1 - only on versions 6 and above. For Powershell 5.1 or below, it's necessary to use .NETFramework instead.
See Resolving PowerShell module assembly dependency conflicts which states the following:
PowerShell and .NET
... In general, PowerShell 5.1 and below run on .NET Framework, while PowerShell 6 and above run on .NET Core. These two
implementations of .NET load and handle assemblies differently.
See Installing PowerShell on Windows for how to get the latest version of Powershell.
In Powershell 7.1.3, one can call a method from MyMathLib.dll by doing the following:
PS C:\Users\Test> [string]$assemblyPath="C:\Temp\MyMathLib.dll"
PS C:\Users\Test> Add-Type -Path $assemblyPath
PS C:\Users\Test> $m1 = New-Object -TypeName MyMathLib.Methods
PS C:\Users\Test> $m1.Sum(2,3)
Note: Since the class/methods aren't static, it's necessary to create an instance by using $m1 = New-Object -TypeName MyMathLib.Methods
However, if it's static (as shown below):
MyMathLib.cs
using System;
namespace MyMathLib
{
public static class Methods
{
public static int Sum(int a, int b)
{
return a + b;
}
public static int Product(int a, int b)
{
return a * b;
}
}
}
Then one can do the following in Powershell:
PS C:\Users\Test> [string]$assemblyPath="C:\Temp\MyMathLib.dll"
PS C:\Users\Test> Add-Type -Path $assemblyPath
PS C:\Users\Test> [MyMathLib.Methods]::Sum(3,2)
Related
This is my .net code
namespace MyMathLib
{
public class Methods
{
public Methods()
{
}
public static int Sum(int a, int b)
{
return a + b;
}
public int Product(int a, int b)
{
return a * b;
}
}
}
and I'm trying to load my dll using powershell
$AssemblyPath = "D:\Visual Studio code\MyMathLib\bin\Debug\net6.0\MyMathLib.dll"
$bytes = [System.IO.File]::ReadAllBytes($AssemblyPath)
[System.Reflection.Assembly]::Load($bytes)
[MyMathLib.Methods]::Sum(10, 2)
It gives below error . dll location is ok.. can anyone please help
Unable to find type [MyMathLib.Methods].
At line:4 char:1
+ [MyMathLib.Methods]::Sum(10, 2)
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (MyMathLib.Methods:TypeName) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound
how to resolve this?`
To write a .NET library that is compatible with both the Desktop/Windows .NET framework edition of Powershell as well as with the .NET Core edition make sure you target .NET standard 2.0 for the library/DLL (and not .NET 6).
So in your VS project use e.g.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>
and not <TargetFramework>net6.0</TargetFramework>. For .NET 6.0 use/install Powershell Core (current release is Powershell 7).
I'm on Windows 10.0.18363.959 with PowerShell 5.1.18362.752
When trying to compile a C# or VB.NET code within PowerShell like this:
$Source = #'
Imports Microsoft.VisualBasic
Imports System
Public NotInheritable Class MainClass
Public Shared Sub Main()
Console.WriteLine($"{True}")
End Sub
End Class
'#
$vbType = Add-Type -TypeDefinition $Source `
-CodeDomProvider (New-Object Microsoft.VisualBasic.VBCodeProvider) `
-PassThru `
-ReferencedAssemblies "Microsoft.VisualBasic.dll", `
"System.dll" `
| where { $_.IsPublic }
[MainClass]::Main()
$Console = [System.Console]
$Console::WriteLine("Press any key to exit...")
$Console::ReadKey($true)
Exit(0)
I get a compiler error because the '$' character used for string interpolation:
C:\Users\Administrador\AppData\Local\Temp\oshdpbp1\oshdpbp1.0.vb(12) : >>> Console.WriteLine($"{1}")
I'm aware that I could use String.Format() function instead, but I would like to know whether this issue can be solved without modifying the original VB.NET code (which of course it compiles right on Visual Studio).
Note that string interpolation was added in VB14. Maybe I'm missing how to specify the right VB compiler version, I don't have idea how to do so with PowerShell.
The VBCodeProvider has a constructor with an IDictionary parameter which allows you to specify the compiler version.
This could work, but I can't test it right now:
$provOptions = [System.Collections.Generic.Dictionary[string,string]]::new()
$provOptions['CompilerVersion'] = 'v14.0'
$vbType = Add-Type -TypeDefinition $Source `
-CodeDomProvider (New-Object Microsoft.VisualBasic.VBCodeProvider $provOptions) `
-PassThru `
-ReferencedAssemblies "Microsoft.VisualBasic.dll", `
"System.dll" `
| where { $_.IsPublic }
I have a simple DotNet DLL like this
namespace ClassLibrary1
{
public class Class1
{
public static void Test()
{
Process.Start("CMD.exe", "/C calc");
}
}
}
When I try to load this DLL with powershell
$Path = "c:\\test\\ClassLibrary1.dll";
$Namespace = "ClassLibrary1";
$ClassName = "Class1";
$Method = "Test";
$Arguments = $null
$Full_Path = [System.IO.Path]::GetFullPath($Path);
$AssemblyName = [System.Reflection.AssemblyName]::GetAssemblyName($Full_Path)
$Full_Class_Name = "$Namespace.$ClassName"
$Type_Name = "$Full_Class_Name, $($AssemblyName.FullName)"
$Type = [System.Type]::GetType($Type_Name)
$MethodInfo = $Type.GetMethod($Method)
$MethodInfo.Invoke($null, $Arguments)
It does not work, because [System.Type]::GetType($Type_Name) returned $null
Any ideas?
Use Add-Type -Path to load your assembly.
After loading, to get a reference to that assembly's [ClassLibrary1.Class1] type as a [Type] instance (to use for reflection) via a string variable, simply cast to [Type].
The following corrected and annotated version of your code demonstrates the approach:
# Compile the C# source code to assembly .\ClassLibrary1.dll
Add-Type -TypeDefinition #'
namespace ClassLibrary1
{
public class Class1
{
public static void Test()
{
System.Diagnostics.Process.Start("CMD.exe", "/C calc");
}
}
}
'# -OutputAssembly .\ClassLibrary1.dll
# Define the path to the assembly. Do NOT use "\\" as the path separator.
# PowerShell doesn't use "\" as the escape character.
$Path = ".\ClassLibrary1.dll"
$Namespace = "ClassLibrary1"
$ClassName = "Class1"
$Method = "Test"
$Arguments = $null
# Load the assembly by its filesystem path, using the Add-Type cmdlet.
# Use of relative paths works.
Add-Type -Path $Path
$Full_Class_Name = "$Namespace.$ClassName"
# To get type [ClassLibrary1.Class1] by its full name as a string,
# simply cast to [type]
$Type = [type] $Full_Class_Name
$MethodInfo = $Type.GetMethod($Method)
$MethodInfo.Invoke($null, $Arguments)
As for what you tried:
As PetSerAl points out, [System.Type]::GetType() can only find a given type if:
its assembly is already loaded
its assembly can be located via standard assembly resolution, as detailed here, which involves many complicated rules, the only two of which likely apply to your scenario is if you're trying to reference a built-in type from the mscorlib assembly, or an assembly located in the GAC (Global Assembly Cache).
By definition, only strongly-named assemblies - those signed with a private key matching the public key mentioned in the assembly's full name - can be placed in the GAC.
While you could have called [System.Reflection.Assembly]::LoadFile($Full_Path) first in order to load the assembly via its filesystem path, after which [System.Type]::GetType($Type_Name) would have succeeded, it is ultimately simpler - and more PowerShell-idiomatic - to use Add-Type -Path to load the assembly (which has the added advantage of not requiring a full (absolute) file path), and, once loaded, to use [Type] with just the type's full name (no reference to the assembly needed anymore).
I'm trying to create a nuget package from my class libary. So far I managed to create and install the package in a test project, but I also want to create a package manager command. My NuGet package is created with the NuGet Package Explorer.
NuGet Package structure
NuGet files content
init.ps1
param($installPath, $toolsPath, $package)
Import-Module (Join-Path $toolsPath MyNuGetCommands.psm1)
MyNuGetCommands.psm1
function Hello($name, $city)
{
Write-Host (‘Hello ‘ + $name + ‘. See you soon in ‘ + $city + ‘.’)
$lib = [Reflection.Assembly]::LoadFile("\\lib\EF.XML.dll")
$obj = new-object Parser
$result = $obj.UpdateXml()
}
Export-ModuleMember Hello
Register-TabExpansion ‘Hello’ #{
‘name’ = { "MSFTees", "MVPs", "My friends" };
‘city’ = { "Redmond", "Seattle", "Bellevue", "Duvall" };
}
I found the Hello function on the net and it worked in my project so I thougt lets add some lines found here to call my C# method. When I call the Hello function in my test project I get this errors:
Error 1
Exception calling "LoadFile" with "1" argument(s): "Cannot find the network path. (Exception from HRESULT: 0x80070035)" At
\svr\redirectedfolders\Stage\my documents\visual studio
2015\Projects\Test\packages\XML.EF.1.0.0\tools\MyNuGetCommands.psm1:5
char:43
+ $lib = [Reflection.Assembly]::LoadFile <<<< ("\lib\EF.XML.dll")
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Error 2
New-Object : Cannot find type [Parser]: make sure the assembly
containing this type is loaded. At
\svr\redirectedfolders\Stage\my documents\visual studio
2015\Projects\Test\packages\XML.EF.1.0.0\tools\MyNuGetCommands.psm1:6
char:22
+ $obj = new-object <<<< Parser
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
Error 3
You cannot call a method on a null-valued expression. At
\svr\redirectedfolders\Stage\my documents\visual studio
2015\Projects\Test\packages\XML.EF.1.0.0\tools\MyNuGetCommands.psm1:7
char:29
+ $result = $obj.UpdateXml <<<< ()
+ CategoryInfo : InvalidOperation: (UpdateXml:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
So I think in the above errors obj is null but how can I fix this (I am not sure if LoadFile path is correct)? I did run set-executionpolicy RemoteSigned in Powershell.
Parser.cs file structure in EF.XML.dll
public class Parser
{
public void UpdateXml()
{
//code
}
}
This is the code to I use to call the method from a .cs file (which works but I want to call this from my Powershell Module:
var parser = new EF.XML.Parser();
parser.UpdateXml();
Following is my powershell script,
function hello()
{
$dllpath = "C:\\Documents and Settings\\raj\\pstest\\testlib.dll";
[Reflection.Assembly]::LoadFrom($dllpath) | out-null;
$obj = New-Object testlib.TestClass;
$obj.print();
}
hello
Following is the TestClass in testlib which i am trying t access in powershell
using System;
namespace testlib
{
class TestClass
{
public TestClass()
{
}
public void print()
{
Console.WriteLine("Hi");
}
}
}
But i am getting error like below,
New-Object : Cannot find type [testlib.TestClass]: make sure the assembly conta
ining this type is loaded.
At C:\Documents and Settings\raj\pstest\script1.ps1:5 char:19
+ $obj = New-Object <<<< testlib.TestClass;
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentExcepti
on
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewOb
jectCommand
You cannot call a method on a null-valued expression.
At C:\Documents and Settings\raj\pstest\script1.ps1:6 char:12
+ $obj.print <<<< ();
+ CategoryInfo : InvalidOperation: (print:String) [], RuntimeExce
ption
+ FullyQualifiedErrorId : InvokeMethodOnNull
I have tried using add-type cmddlet but it is also giving the same response.
I guess the dll is getting loaded properly into the powershell but I am not able to instantiate the object of TestClass. Please tell me what I am doing wrong.
If i remove out-null following is the output im getting,
GAC Version Location
--- ------- --------
False v2.0.50727 C:\Documents and Settings\553566\pstest\testlib.dll
New-Object : Cannot find type [testlib.TestClass]: make sure the assembly conta
ining this type is loaded.
At C:\Documents and Settings\raj\pstest\script1.ps1:5 char:19
+ $obj = New-Object <<<< testlib.TestClass;
+ CategoryInfo : InvalidType: (:) [New-Object], PSArgumentExcepti
on
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewOb
jectCommand
You cannot call a method on a null-valued expression.
At C:\Documents and Settings\raj\pstest\script1.ps1:6 char:12
+ $obj.print <<<< ();
+ CategoryInfo : InvalidOperation: (print:String) [], RuntimeExce
ption
+ FullyQualifiedErrorId : InvokeMethodOnNull
Try this:
[code]$dll = $($env:userprofile + '\pstest\testdll.dll')
And then try to invoke some static method from your library. For example:
$dll = $($env:userprofile + '\pstest\test.dll')
[void][Reflection.Assembly]::LoadFile($dll)
[MyNamespace.MyClass]::Print()
OOPS my bad .........
The TestClass should be a public one :(
After that modification its working