我有一个PowerShell脚本,如下所示:
Import-Module module1.dll
Import-Module module2.dll
Get-SomethingFromModule1 | Get-SomethingFromModule2
我遇到的问题是,module1.dll
和module2.dll
都引用了SomeOtherLibrary.dll
的不同版本,SomeOtherLibrary.dll
的版本包含了我发生的重大变化使用
我可以跑
Import-Module module1.dll
Get-SomethingFromModule1
和
Import-Module module2.dll
Get-SomethingFromModule2
在单独的PowerShell会话中,每个都表现正确。
但是,我想将数据从一个cmdlet传递到另一个cmdlet,Get-SomethingFromModule2
由于找不到方法而引发异常。我相信只加载/使用SomeOtherLibrary.dll
的最新版本(或导入的第一个模块使用的版本)。有没有办法强制module1.dll
和module2.dll
加载/使用特定版本的SomeOtherLibrary.dll
?
我试图避免更新引用并重新编译所有这些模块。
谢谢
答案 0 :(得分:2)
我通过在.csproj文件中提供强名称密钥文件来强烈命名程序集SomeOtherLibrary.dll
:
<PropertyGroup>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
我现在可以导入两个模块,每个模块都使用自己版本的程序集SomeOtherLibrary.dll
。这种方法仍然需要我更新引用并重新编译所有这些模块。
但是,只要我强烈命名所有PowerShell模块引用的程序集,它就可以防止将来发生此问题。
答案 1 :(得分:0)
如果强命名不是一个选项,您还可以在单独的appdomains中运行cmdlet逻辑,从而在Powershell加载和运行代码时保持依赖关系的隔离。通过一些简单的优化,如果考虑性能,单独的appdomain版本也可以相当快地运行。
假设您有一个看起来像这样的cmdlet:
using System.Management.Automation;
namespace CmdletOne
{
[Cmdlet(VerbsCommon.Show, "Path")]
public class ShowPathCmdlet : Cmdlet
{
protected override void ProcessRecord()
{
// ...
// Run some business logic & load assemblies
// ...
WriteObject("Value is foo");
}
}
}
为了重构这个,你需要做四件事。首先,您需要代码来管理appdomain创建和远程处理。我使用过这样的辅助类:
using System;
using System.IO;
namespace Common
{
/// <summary>
/// General-purpose class that can put a remoting proxy around a given type and create a new appdomain for it to run in.
/// This effectively "sandboxes" the code being run and isolates its dependencies from other pieces of code.
/// </summary>
/// <typeparam name="T">The type of object that will be run in the sandbox. Must be compatible with Remoting.</typeparam>
public class ExecutionSandbox<t> : IDisposable
where T : MarshalByRefObject
{
/// <summary>
/// Local copy of the sandbox app domain
/// </summary>
private AppDomain _domain;
/// <summary>
/// Reference of the proxy wrapper for T
/// </summary>
public T ObjectProxy { get; private set; }
/// <summary>
/// Creates an instance of ExecutionSandbox
/// </summary>
/// <param name="assemblyPath" />The path where the assembly that contains type T may be found
public ExecutionSandbox(string assemblyPath)
{
Type sandboxedType = typeof (T);
AppDomainSetup domainInfo = new AppDomainSetup();
domainInfo.ApplicationBase = assemblyPath;
_domain = AppDomain.CreateDomain(string.Format("Sandbox.{0}", sandboxedType.Namespace), null, domainInfo);
string assemblyFileName = Path.Combine(assemblyPath, sandboxedType.Assembly.GetName().Name) + ".dll";
object instanceAndUnwrap = _domain.CreateInstanceFromAndUnwrap(assemblyFileName, sandboxedType.FullName);
ObjectProxy = (T)instanceAndUnwrap;
}
/// <summary>
/// Allows safe cleanup of the sandbox app domain.
/// </summary>
public void Dispose()
{
if (_domain != null)
{
AppDomain.Unload(_domain);
_domain = null;
}
ObjectProxy = null;
}
}
}
您需要的第二件事是一个界面,它定义您的代码将在单独的appdomain中执行的操作。这篇文章实际上非常重要,但在撰写本文时我不确定为什么 - 我可能会发布一个我自己的问题。我很简单,就像这样:
namespace CmdletOne
{
public interface IProxy
{
string DoWork();
}
}
第三,您需要在代码周围创建一个包装器。此包装器的类型将用作远程代理,其代码将在单独的appdomain中运行。我看起来像这样 - 注意与原始cmdlet的相似性,并注意继承链:
using System;
using Common;
namespace CmdletOne
{
public class Proxy : MarshalByRefObject, IProxy
{
public string DoWork()
{
// ...
// Run some business logic & load assemblies
// ...
return "foo";
}
}
}
最后,您需要重构原始cmdlet,以便使用单独的appdomain执行代码。我看起来像这样,其中包括一些额外的代码来优化性能:
using System;
using System.IO;
using System.Management.Automation;
using System.Reflection;
using Common;
namespace CmdletOne
{
[Cmdlet(VerbsCommon.Show, "Path")]
public class ShowPathCmdlet : Cmdlet
{
private static ExecutionSandbox _executionSandbox;
private readonly object _lockObject = new object();
protected override void ProcessRecord()
{
DateTime start = DateTime.Now;
lock (_lockObject)
{
if (_executionSandbox == null)
{
string cmdletExecutionPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
_executionSandbox = new ExecutionSandbox(cmdletExecutionPath);
}
}
Proxy proxy = _executionSandbox.Value;
string path = proxy.DoWork();
DateTime end = DateTime.Now;
WriteObject(string.Format("Value is {0}. Elapsed MS: {1}", path, (end - start).TotalMilliseconds));
}
}
}
有关此技术的更多详细信息,包括示例代码的链接,可以找到here。