我有一个完全用 C# 编写的 PS 模块。它包含大约 20 个已投入生产的 Cmdlet。其中一些“共享代码”。举个例子:
我有一个名为的 Cmdlet InvokeCommitCommand,可以生成“变更集”。此 Cmdlet 还发布此变更集的元数据。我现在想创建一个名为 的新 Cmdlet PublishCommitCommand,可以独立调用它来执行现有变更集的“发布”。因此,我想重构InvokeCommitCommand以利用新的 CmdletPublishCommitCommand并避免代码重复。
更一般地说...我试图CommandB从 cmdlet调用 cmdlet CommandA。它们的定义如下
public CommandA : PSCmdlet
{
...
}
public CommandB : PSCmdlet
{
...
}
Run Code Online (Sandbox Code Playgroud)
我这里有几个选择。但它们都不起作用。
1. 选项
CommandB通过创建它的实例来调用。这是我的第一个猜测。就像这样:
var cmd = new CommandB();
cmd.Invoke();
Run Code Online (Sandbox Code Playgroud)
不幸的是,这不起作用。我得到例外:
无法直接调用从 PSCmdlet 派生的 Cmdlet ...
所以...下一个选择。
2. 选项
创建 PowerShell 实例并运行命令。就像这样:
var ps = PowerShell.Create();
ps.AddCommand("CommandB");
ps.Invoke();
Run Code Online (Sandbox Code Playgroud)
不幸的是这也不起作用。这会导致创建一个新的 PowerShell 实例,因此我会丢失可能已附加到当前正在运行的 PowerShell 实例的所有流重定向。
我知道我可以重用运行空间。但使用相同的运行空间并不能避免我丢失重定向。如果CommandB我打电话Write-Verbose "Huzzah!",我就不会看到“哈扎!” 任何地方。
简而言之:我需要CommandB在同一个 PS 实例中运行CommandA
3. 选项
使用脚本块。就像这样:
var sb = ScriptBlock.Create("CommandB");
sb.Invoke();
Run Code Online (Sandbox Code Playgroud)
那真是太好了。但这里的问题是,我无法将任何复杂的类参数传递给脚本块。如果CommandB有一个 ... 类型的参数PSCredential,那么我没有简单的方法将该参数传递给脚本。如果我有一个PowerShell对象,我可以轻松做到
PowerShell ps
ps.AddCommand("CommandB");
ps.AddArgument("Credential", someCredentialObject);
ps.AddArgument("TargetUri", new Uri("www.google.de"));
Run Code Online (Sandbox Code Playgroud)
但我不能用一个ScriptBlock。确实,我可以使用InvokeWithContext它允许我将变量传递给脚本块,但我需要先将每个复杂的参数“包装”在变量中......相当麻烦。
结论
有任何想法吗?最好的事情是,如果我能以某种方式从内部CommandA访问PowerShell我正在运行的当前实例。然后我可以利用选项 2,而不会出现创建新实例的问题。但我不知道这是否可能......
我最终提出的解决方案是一个实现PetSerAl建议的方法的辅助类。我使用像上面第三个选项一样的 ScriptBlock,但进行了一些更改以使传递参数不那么繁琐。
所以这是我的助手类,它很好地完成了这项工作:
public class PsInvoker
{
public static PSObject[] InvokeCommand(string commandName, Hashtable parameters)
{
var sb = ScriptBlock.Create("param($Command, $Params) & $Command @Params");
return sb.Invoke(commandName, parameters).ToArray();
}
public static PSObject[] InvokeCommand<T>(Hashtable parameters) where T : Cmdlet
{
return InvokeCommand(Extensions.GetCmdletName<T>(), parameters);
}
public static PsInvoker Create(string cmdletName)
{
return new PsInvoker(cmdletName);
}
public static PsInvoker Create<T>() where T : Cmdlet
{
return new PsInvoker(Extensions.GetCmdletName<T>());
}
private Hashtable Parameters { get; set; }
public string CmdletName { get; }
public bool Invoked { get; private set; }
public PSObject[] Result { get; private set; }
private PsInvoker(string cmdletName)
{
CmdletName = cmdletName;
Parameters = new Hashtable();
}
public void AddArgument(string name, object value)
{
Parameters.Add(name, value);
}
public void AddArgument(string name)
{
Parameters.Add(name, null);
}
public PSObject[] Invoke()
{
if (Invoked)
throw new InvalidOperationException("This instance has already been invoked.");
var sb = ScriptBlock.Create("param($Command, $Params) & $Command @Params");
Result = sb.Invoke(CmdletName, Parameters).ToArray();
Invoked = true;
return Result;
}
}
Run Code Online (Sandbox Code Playgroud)
该类基本上提供了两种调用 cmdlet 的方法:
InvokeCommand并将 cmdlet 的名称以及任何参数作为Hashtable.AddArgument添加参数,然后用于Invoke运行 cmdlet。再次感谢 PetSerAl。
| 归档时间: |
|
| 查看次数: |
2691 次 |
| 最近记录: |