如何让PowerShell添加的类型使用添加的类型

如何让PowerShell添加的类型使用添加的类型,powershell,code-generation,powershell-2.0,Powershell,Code Generation,Powershell 2.0,我正在做一个生成CSharp代码的高级项目,然后将类型添加到内存中 新类型使用磁盘上DLL中的现有类型,该DLL通过“添加类型”加载 在我真正尝试调用新类型的方法之前,一切都很好。下面是我正在做的一个例子: $PWD = "." rm -Force $PWD\TestClassOne* $code = " namespace TEST{ public class TestClassOne { public int DoNothing() { return 1;

我正在做一个生成CSharp代码的高级项目,然后
将类型添加到内存中

新类型使用磁盘上DLL中的现有类型,该DLL通过“添加类型”加载

在我真正尝试调用新类型的方法之前,一切都很好。下面是我正在做的一个例子:

$PWD = "."
rm -Force $PWD\TestClassOne*
$code = "
namespace TEST{
public class TestClassOne
{
    public int DoNothing()
    {
        return 1;
    }
}
}"
$code | Out-File tcone.cs
Add-Type -OutputAssembly $PWD\TestClassOne.dll -OutputType Library -Path $PWD\tcone.cs
Add-Type -Path $PWD\TestClassOne.dll
$a = New-Object TEST.TestClassOne
"Using TestClassOne"
$a.DoNothing()


"Compiling TestClassTwo"
Add-Type -Language CSharpVersion3 -TypeDefinition "
namespace TEST{
public class TestClassTwo
{
    public int CallTestClassOne()
    {
        var a = new TEST.TestClassOne();
        return a.DoNothing();
    }
}
}" -ReferencedAssemblies $PWD\TestClassOne.dll
"OK"
$b = New-Object TEST.TestClassTwo
"Using TestClassTwo"
$b.CallTestClassOne()
运行上述脚本会在最后一行出现以下错误:

使用“0”参数调用“CallTestClassOne”时发生异常: 无法加载文件或程序集“TestClassOne,…” 或其依赖项之一。系统找不到指定的文件。“ 在AddTypeTest.ps1:39字符:20
+$b.CallTestClassOne当您将
TestClassTwo
输出到dll(与
TestClassOne
位于同一目录中)并
添加类型
时,它会工作。或者至少是我的机器;)这就是丑陋的解决方法

调用
$b.CallTestClassOne()时,PowerShell会尝试(出于某种原因,我不知道)在以下位置查找程序集TestClassOne.dll:

LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.DLL
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne.EXE
LOG: Pokus o stažení nové adresy URL file:///C:/Windows/SysWOW64/WindowsPowerShell/v1.0/TestClassOne/TestClassOne.EXE
这是fuslogvw工具的输出。它可能对你有用。使用ProcessMonitor可以实时查看相同的路径列表

您也可以尝试(在调用
CallTestClassOne()之前)

这将向您显示什么程序集失败以及一些其他信息


我同意它应该像您期望的那样工作。这就是为什么它看起来有点问题。

发生这种情况是因为应用程序(PowerShell)中的CLR加载程序查找任何程序集基本目录。当然,它在那里找不到您的程序集。解决此问题的最佳方法是挂接stej提到的AssemblyResolve事件,但使用它告诉CLR程序集在哪里。PowerShell 2.0的Register ObjectEvent不能这样做,因为它不适用于需要返回值的事件(即程序集).在这种情况下,让我们使用更多的C#via Add Type来完成这项工作。这段代码可以:

ri .\TestClassOne.dll -for -ea 0

$resolver = @'
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Utils
{
    public static class AssemblyResolver
    {
        private static Dictionary<string, string> _assemblies;

        static AssemblyResolver()
        {
            var comparer = StringComparer.CurrentCultureIgnoreCase;
            _assemblies = new Dictionary<string,string>(comparer);
            AppDomain.CurrentDomain.AssemblyResolve += ResolveHandler;
        }

        public static void AddAssemblyLocation(string path)
        {
            // This should be made threadsafe for production use
            string name = Path.GetFileNameWithoutExtension(path);
            _assemblies.Add(name, path);
        }

        private static Assembly ResolveHandler(object sender, 
                                               ResolveEventArgs args) 
        {
            var assemblyName = new AssemblyName(args.Name);
            if (_assemblies.ContainsKey(assemblyName.Name))
            {
                return Assembly.LoadFrom(_assemblies[assemblyName.Name]);
            }
            return null;
        }
    }
}
'@

Add-Type -TypeDefinition $resolver -Language CSharpVersion3

$code = @'
namespace TEST {
    public class TestClassOne {
        public int DoNothing() {
            return 1;
        }
    }
}
'@
$code | Out-File tcone.cs
Add-Type -OutputAssembly TestClassOne.dll -OutputType Library -Path tcone.cs

# This is the key, register this assembly's location with our resolver utility
[Utils.AssemblyResolver]::AddAssemblyLocation("$pwd\TestClassOne.dll")

Add-Type -Language CSharpVersion3 `
         -ReferencedAssemblies "$pwd\TestClassOne.dll" `
         -TypeDefinition @'
namespace TEST {
    public class TestClassTwo {
        public int CallTestClassOne() {
            var a = new TEST.TestClassOne();
            return a.DoNothing();
        }
    }
}
'@ 

$b = new-object Test.TestClassTwo
$b.CallTestClassOne()
ri.\TestClassOne.dll-for-ea 0
$resolver=@'
使用制度;
使用System.Collections.Generic;
使用System.IO;
运用系统反思;
命名空间Utils
{
公共静态类AssemblyResolver
{
私有静态字典_程序集;
静态汇编解析器()
{
var comparer=StringComparer.CurrentCultureInoRecase;
_程序集=新字典(比较器);
AppDomain.CurrentDomain.AssemblyResolve+=ResolveHandler;
}
公共静态void AddAssemblyLocation(字符串路径)
{
//这应该是线程安全的生产使用
string name=Path.GetFileNameWithoutExtension(Path);
_添加(名称、路径);
}
私有静态程序集ResolveHandler(对象发送方,
ResolveEventArgs(参数)
{
var assemblyName=新的assemblyName(args.Name);
if(_assemblies.ContainsKey(assemblyName.Name))
{
返回Assembly.LoadFrom(_Assembly[assemblyName.Name]);
}
返回null;
}
}
}
'@
添加类型-类型定义$resolver-语言CSharpVersion3
$code=@'
名称空间测试{
公共类TestClassOne{
公共事务{
返回1;
}
}
}
'@
$code |输出文件tcone.cs
添加类型-OutputAssembly TestClassOne.dll-OutputType库-Path tcone.cs
#这是密钥,请使用解析程序实用程序注册此程序集的位置
[Utils.AssemblyResolver]::AddAssemblyLocation($pwd\TestClassOne.dll)
添加类型-语言CSharpVersion3`
-ReferenceAssemblys“$pwd\TestClassOne.dll”`
-类型定义@'
名称空间测试{
公共类TestClass2{
public int CallTestClassOne(){
var a=newtest.TestClassOne();
返回a.DoNothing();
}
}
}
'@ 
$b=新对象Test.testClass2
$b.CallTestClassOne()

谢谢。我认为Add-Type没有得到它需要的API可用性测试。它在这些位置进行查找,因为那是应用程序(PowerShell)的基本目录-[appdomain]::CurrentDomain.BaseDirectory。CLR加载程序将在该位置的某个子目录下或该位置的某个子目录下查找,除非向PowerShell.exe的配置文件添加专用探测路径(不推荐)@Keith,你是对的。但是,问题是为什么PowerShell会尝试加载程序集,即使程序集应该已经加载。这很奇怪:|我认为这与加载每个程序集期间使用的加载上下文有关。从Reflector中可以看出,第一次加载(添加类型-路径)使用LoadFrom上下文。我怀疑第二个程序集加载使用了加载上下文。请查看此网站上的表。加载上下文列出了一个缺点:很好!;)但是,华丽的行为很棘手,因为人们可能会认为第一个程序集已经加载。
ri .\TestClassOne.dll -for -ea 0

$resolver = @'
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Utils
{
    public static class AssemblyResolver
    {
        private static Dictionary<string, string> _assemblies;

        static AssemblyResolver()
        {
            var comparer = StringComparer.CurrentCultureIgnoreCase;
            _assemblies = new Dictionary<string,string>(comparer);
            AppDomain.CurrentDomain.AssemblyResolve += ResolveHandler;
        }

        public static void AddAssemblyLocation(string path)
        {
            // This should be made threadsafe for production use
            string name = Path.GetFileNameWithoutExtension(path);
            _assemblies.Add(name, path);
        }

        private static Assembly ResolveHandler(object sender, 
                                               ResolveEventArgs args) 
        {
            var assemblyName = new AssemblyName(args.Name);
            if (_assemblies.ContainsKey(assemblyName.Name))
            {
                return Assembly.LoadFrom(_assemblies[assemblyName.Name]);
            }
            return null;
        }
    }
}
'@

Add-Type -TypeDefinition $resolver -Language CSharpVersion3

$code = @'
namespace TEST {
    public class TestClassOne {
        public int DoNothing() {
            return 1;
        }
    }
}
'@
$code | Out-File tcone.cs
Add-Type -OutputAssembly TestClassOne.dll -OutputType Library -Path tcone.cs

# This is the key, register this assembly's location with our resolver utility
[Utils.AssemblyResolver]::AddAssemblyLocation("$pwd\TestClassOne.dll")

Add-Type -Language CSharpVersion3 `
         -ReferencedAssemblies "$pwd\TestClassOne.dll" `
         -TypeDefinition @'
namespace TEST {
    public class TestClassTwo {
        public int CallTestClassOne() {
            var a = new TEST.TestClassOne();
            return a.DoNothing();
        }
    }
}
'@ 

$b = new-object Test.TestClassTwo
$b.CallTestClassOne()