Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
394 views
in Technique[技术] by (71.8m points)

code generation - How can I get PowerShell Added-Types to use Added Types

I'm working on a PoSh project that generates CSharp code, and then Add-Types it into memory.

The new types use existing types in an on disk DLL, which is loaded via Add-Type.

All is well and good untill I actualy try to invoke methods on the new types. Here's an example of what I'm doing:

$PWD = "."
rm -Force $PWDTestClassOne*
$code = "
namespace TEST{
public class TestClassOne
{
    public int DoNothing()
    {
        return 1;
    }
}
}"
$code | Out-File tcone.cs
Add-Type -OutputAssembly $PWDTestClassOne.dll -OutputType Library -Path $PWDcone.cs
Add-Type -Path $PWDTestClassOne.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 $PWDTestClassOne.dll
"OK"
$b = New-Object TEST.TestClassTwo
"Using TestClassTwo"
$b.CallTestClassOne()

Running the above script gives the following error on the last line:

Exception calling "CallTestClassOne" with "0" argument(s): "Could not load file or assembly 'TestClassOne,...' or one of its dependencies. The system cannot find the file specified." At AddTypeTest.ps1:39 char:20 + $b.CallTestClassOne <<<< () + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : DotNetMethodException

What am I doing wrong?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This happens because any assemblies are looked for by the CLR loader in the application's (PowerShell's) base directory. Of course, it doesn't find your assembly there. The best way to solve this is to hook the AssemblyResolve event as stej mentions but use it to tell the CLR where the assembly is. You can't do this with PowerShell 2.0's Register-ObjectEvent because it doesn't work with events that require a return value (ie the assembly). In this case, let's use more C# via Add-Type to do this work for us. This snippet of code works:

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("$pwdTestClassOne.dll")

Add-Type -Language CSharpVersion3 `
         -ReferencedAssemblies "$pwdTestClassOne.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()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...