Как мога да накарам PowerShell Added-Types да използва Added Types

Работя върху PoSh проект, който генерира CSharp код и след това го Add-Types в паметта.

Новите типове използват съществуващи типове в 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()

Изпълнението на горния скрипт дава следната грешка на последния ред:

Изключение, извикващо „CallTestClassOne“ с аргумент(и) „0“: „Не може да се зареди файл или сборка „TestClassOne,...“ или една от неговите зависимости. Системата не може да намери посочения файл.“ В AddTypeTest.ps1:39 char:20 + $b.CallTestClassOne ‹‹‹‹ () + CategoryInfo: NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId: DotNetMethodException

какво правя грешно


person Scott Weinstein    schedule 18.04.2010    source източник


Отговори (2)


Това се случва, защото всички асембли се търсят от CLR товарача в базовата директория на приложението (PowerShell). Разбира се, не намира вашето събрание там. Най-добрият начин да разрешите това е да закачите събитието AssemblyResolve, както стей споменава, но да го използвате, за да кажете на CLR къде е асемблирането. Не можете да направите това с Register-ObjectEvent на PowerShell 2.0, тъй като не работи със събития, които изискват връщана стойност (т.е. асемблирането). В този случай нека използваме повече C# чрез 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()
person Keith Hill    schedule 19.04.2010
comment
добър! ;) Поведението на Posh обаче е сложно, защото човек би очаквал, че първата сглобка вече е заредена. - person stej; 19.04.2010

Когато изведете 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

Това ще ви покаже какво сглобяване е неуспешно и малко повече информация.

Съгласен съм, че трябва да работи както очаквате. Ето защо това изглежда някак бъги.

person stej    schedule 19.04.2010
comment
Благодаря за това. Мисля, че Add-Type не получи необходимия тест за използваемост на API. - person Scott Weinstein; 19.04.2010
comment
Изглежда в тези местоположения, защото това е основната директория на приложението (PowerShell) - [appdomain]::CurrentDomain.BaseDirectory. CLR товарачът ще търси там или под определени поддиректории на това местоположение, освен ако не добавите частен път за сондиране към конфигурационния файл на PowerShell.exe (не се препоръчва). - person Keith Hill; 19.04.2010
comment
@Keith, прав си. Въпросът обаче е защо PowerShell се опитва да зареди сглобката, въпреки че сглобката вече трябва да е заредена. Това е странно :| - person stej; 19.04.2010
comment
Вярвам, че това е свързано с това кой контекст на зареждане се използва по време на зареждането на всяка сглобка. От Reflector изглежда, че първото зареждане (Add-Type -Path) използва контекста LoadFrom. Подозирам, че второто зареждане на асемблиране използва контекста на зареждане. Вижте таблицата на този сайт. Има един недостатък, посочен за контекста на зареждане: blogs.msdn. com/suzcook/archive/2003/05/29/57143.aspx - person Keith Hill; 20.04.2010