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

时间:2010-04-18 21:25:53

标签: powershell code-generation powershell-v2.0

我正在开发一个生成CSharp代码的PoSh项目,然后将Add-Type放入内存中。

新类型使用磁盘DLL中的现有类型,该DLL通过Add-Type加载。

一切都很好,直到我实际上尝试调用新类型的方法。这是我正在做的一个例子:

$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 char:20 + $ b.CallTestClassOne<<<< ()     + CategoryInfo:NotSpecified:(:) [],MethodInvocationException     + FullyQualifiedErrorId:DotNetMethodException

我做错了什么?

2 个答案:

答案 0 :(得分:7)

这是因为应用程序(PowerShell)基目录中的CLR加载程序会查找任何程序集。当然,它没有在那里找到你的组装。解决此问题的最佳方法是将AssemblyResolve事件挂钩为stej提及,但使用它来告诉CLR汇编的位置。您无法使用PowerShell 2.0的Register-ObjectEvent执行此操作,因为它不适用于需要返回值的事件(即程序集)。在这种情况下,让我们通过Add-Type使用更多C#来为我们完成这项工作。这段代码有效:

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()

答案 1 :(得分:4)

当您将TestClassTwo输出到dll(与TestClassOne在同一目录中)和Add-Type时,它可以正常工作。或者至少在我的机器上;)这就是丑陋的解决方法。

调用$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()

之前)
[appdomain]::CurrentDomain.add_assemblyResolve({
    $global:x = $args
})
$b.CallTestClassOne()
$x | fl

这将显示装配失败的原因以及更多信息。

我同意它应该按预期工作。所以这就是为什么这看起来有些错误。