没有理由ScriptBlock
直接在您的 C# 代码中创建实例并与之交互 - 它们由 PowerShell SDK 在内部使用:[1]当您将一段 PowerShell 代码作为字符串传递给PowerShell.AddScript()
方法时,它们是在内部创建和存储的,并且通过PowerShell
实例的,.Invoke()
方法调用。
虽然您通过让实例PowerShell
创建并通过.AddScript()
调用ps.AddScript( pSScript ); var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;
( _ _直接在 C# 代码中创建脚本块,由于未连接到PowerShell 运行空间,您将无法全部调用它),此类调用仅提供成功输出-所有其他PowerShell 流的输出将丢失- 即,原始实例的.Invoke()
PowerShell
.Streams
属性不会反映这样的输出,这会导致发生的非终止错误无法访问,同样,该.HadErrors
属性也不会反映是否发生了非终止错误。因此,应该避免这种方法。[2]
这是一个隐式创建脚本块的示例,在幕后,通过PowerShell.AddScript()
,将参数传递给它并调用它:
// Define the script-block text.
// It expects a single argument that is an object with .Name and .Code
// properties, whose values are echoed.
// NOTE: Do NOT include { ... }
var scriptBlockText = "$obj = $args[0]; $obj.Name; $obj.Code";
// Define an object to pass to the script block.
var obj = new { Name = "Abc", Code = 42 };
using (var ps = PowerShell.Create()) {
// Add the script block and an argument to pass to it.
ps
.AddScript(scriptBlockText)
.AddArgument(obj);
// Invoke and echo the results.
foreach (var o in ps.Invoke()) {
Console.WriteLine(o);
}
}
但是,上述内容不可重用,因为一旦您使用 or 添加了参数或参数.AddParameter(s)
,.AddArgument()
就无法删除它们并指定不同的参数来执行另一个调用 - 据我所知。
解决方法是使用 PowerShell管道输入(通过input
您可以传递给的可选参数提供PowerShell.Invoke()
,因为这样可以使用不同的输入进行重复调用。
但是,您的脚本块必须相应地构造:
// Define the script-block text.
// This time, expect the input to come via the *pipeline*, which
// can be accessed via the $input enumerator.
// NOTE: Do NOT include { ... }
var scriptBlockText = "$obj = $($input); $obj.Name; $obj.Code";
// Define two objects to pass to the script block, one each in
// two separate invocations:
object[] objects = {
new { Name = "Abc", Code = 42 },
new { Name = "Def", Code = 43 }
};
using (var ps = PowerShell.Create()) {
// Add the script block.
ps.AddScript(scriptBlockText);
// Perform two separate invocations.
foreach (var obj in objects) {
// For housekeeping, clean the previous non-success streams.
ps.Streams.ClearStreams();
// Invoke the script block with the object at hand and echo the results.
// Note: The input argument must be an enumerable, so we wrap the object
// in an aux. array.
foreach (var o in ps.Invoke(new[] { obj })) {
Console.WriteLine(o);
}
}
}
或者,如果可行,请考虑不使用脚本块,因为它们需要解析(尽管在这种情况下是一次性开销)并且 - 在 Windows 上 - 受有效执行策略的约束,这可能会阻止它们的执行(尽管您可以绕过对每个进程的这种限制,请参阅此答案)。
如果没有脚本块,您必须单独调用一个或多个命令,使用PowerShell.AddCommand()
调用,用PowerShell.AddStatement()
.
使可重用技术适应您的代码:
Class DataRulesPSScripts
,它使用静态PowerShell
实例并在其静态构造函数中添加一次脚本块。
- 注意:考虑改用实例属性并使类实现
IDisposable
以允许类用户控制 PowerShell 实例的生命周期。
class DataRulesPSScripts
{
static PowerShell ps = PowerShell.Create();
// The script-block text:
// Note that $ParamA and $ParamB must correspond to the keys of the
// dictionary passed to the script block on invocation via .InvokeAsync()
static string PSScript = @"$argDict = $($input); & { param($ParamA, $ParamB) [pscustomobject] @{ Partition = $ParamA; Key = 1 }, [pscustomobject] @{ Row = $ParamB; Key = 2 } } @argDict";
static DataRulesPSScripts() {
// Add the script-block text only once, which therefore incurs the
// overhead of parsing the text into a script block only once,
// and allows repeated later invocations via .Invoke() with pipeline input.
ps.AddScript(PSScript);
}
public async Task<IEnumerable<object>> RunScriptBlock(Dictionary<string, EntityProperty> scriptParameters)
{
// Pass the parameter dictionary as pipeline input.
// Note: Since dictionaries are enumerable themselves, an aux. array
// is needed to pass the dictionary as a single object.
return await ps.InvokeAsync<object>(new [] { scriptParameters });
}
}
使用类的代码,通过管道传递参数:
internal async Task<string> GeneratePartitionKey(Dictionary<string, EntityProperty> arg)
{
var result = await GenerateKeys(arg);
return result[0].ToString();
}
internal async Task<string> GenerateRowKey(Dictionary<string, EntityProperty> arg)
{
var result = await GenerateKeys(arg);
return result[1].ToString();
}
private async Task<List<object>> GenerateKeys(Dictionary<string, EntityProperty> args)
{
DataRulesPSScripts ds = new DataRulesPSScripts();
var results = await ds.RunScriptBlock(args);
return results.ToList();
}
示例调用(obj
是包含上述方法的对象;假定EntityProperty
具有属性的简化类.Value
):
Console.WriteLine(
obj.GenerateRowKey(
new Dictionary<string, EntityProperty> { ["ParamA"] = new EntityProperty { Value = "bar" }, ["ParamB"] = new EntityProperty { Value = "baz" } }
).Result
);
上面应该产生类似的东西:
@{Row=demo.EntityProperty; Key=2}
这是脚本块输出的第二个自定义对象的字符串表示形式。
[1]相比之下,在PowerShell 脚本代码ScriptBlock
中,直接使用实例,通常以脚本块 文字( { ... }
)的形式&
,通过调用运算符 调用。
[2] 这是来自 PowerShell 的快速演示:
$ps=[PowerShell]::Create(); $sb = $ps.AddScript("{ 'hi'; Get-Item nosuchfile }").Invoke()[0]; "call result: $($sb.Invoke())"; "had errors: $($ps.HadErrors)"; "error stream: $($ps.Streams.Error)"
即使调用产生了非终止错误,也会.HadErrors
报告$false
,并且.Streams.Error
是空的。