问题描述
我有下面的方法,它被不同的 PS 脚本调用,我希望 PowerShell 对象只创建一次,为此我将 Powershell 对象设为静态(见下面的代码)。但随后它给了我错误
当前 PowerShell 实例的状态对此无效 操作。
我该如何处理?优化我下面的代码的最佳方法是什么? 注意:如果我删除静态,下面的代码可以正常工作。
class DataRulesPSScripts
{
static PowerShell ps = PowerShell.Create();
public IEnumerable<object> RunScriptBlock( ScriptBlock scriptBlock,Dictionary<string,object> scriptParameters )
{
var vars = scriptParameters.Select( p => new PSVariable( p.Key,p.Value ) ).ToList();
return scriptBlock.InvokeWithContext( null,vars );
}
public async Task<ScriptBlock> CreateScriptBlock( string pSScript )
{
ps.AddScript( pSScript );
var scriptBlock = (await ps.InvokeAsync())[0].BaSEObject as ScriptBlock;
return scriptBlock;
}
}
}
这是从这里调用的:
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> arg )
{
var pars = new Dictionary<string,object>();
pars.Add( "_",arg );
DataRulesPSScripts ds = new DataRulesPSScripts();
var scriptBlock = await ds.CreateScriptBlock( PSScript );
var results = ds.RunScriptBlock( scriptBlock,pars ).ToList();
return results;
}
解决方法
没有理由直接在 C# 代码中创建 ScriptBlock
实例并与之交互 - 它们由 PowerShell SDK内部使用: [1] 当您将一段 PowerShell 代码作为 字符串 传递给 PowerShell.AddScript()
方法时,它们是在内部创建和存储的,并通过 {{ 1}} 个实例的,.Invoke()
方法。
虽然您的间接方式是通过让 PowerShell
实例创建和通过 PowerShell
调用 (.AddScript()
) 为您输出它确实 为您提供一个脚本块,您可以通过其 ps.AddScript( pSScript ); var scriptBlock = (await ps.InvokeAsync())[0].BaseObject as ScriptBlock;
方法直接从 C# 代码调用(如果您直接在 C# 代码中创建了脚本块,由于未连接到 PowerShell runspace),您将无法全部调用它,此类调用仅提供成功 输出 - 所有其他 PowerShell streams 的输出将丢失 - 即原始 .Invoke()
实例的 {{ 1}} 属性不会反映此类输出,这尤其会使发生的非终止错误不可访问,同样,PowerShell
属性不会反映是否发生了非终止错误。因此,这种方法应该避免。[2]
这是一个在幕后隐式地创建脚本块的示例,通过PowerShell.AddScript()
,将参数传递给它并调用它:
.Streams
但是,上述内容不能重用,因为一旦您添加了带有 .HadErrors
或 // 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);
}
}
的参数或参数,您就无法删除它们并指定不同的来执行另一个呼叫 - 据我所知。
- GitHub issue #15004 包含相关讨论。
解决方法是使用 PowerShell 管道输入(通过可选的 .AddParameter(s)
参数提供,您可以传递给 PowerShell.Invoke()
,因为 允许使用不同的输入重复调用。
但是,您的脚本块必须相应地构建:
.AddArgument()
或者,如果可行,考虑制作没有脚本块,因为它们需要解析(尽管在这种情况下是一次性开销)并且 - 在 Windows 上 -受有效 execution policy 的约束,这可能会阻止其执行(尽管您可以在每个进程的基础上绕过此类限制,请参阅 this answer)。
如果没有脚本块,您必须单独调用一个或多个命令,使用 PowerShell.AddCommand()
调用,用 PowerShell.AddStatement()
分隔多个独立命令。
-
如果单个命令或命令管道接受所有输入通过管道,您可以使用与上述相同的方法。
-
否则 - 如果需要
input
/// 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); } } }
- 您必须调用.AddParameter(s)
并在每次(重复)调用之前重新添加命令;然而,与调用.AddArgument()
相比,这应该引入很少的开销。
使可重用技术适应您的代码:
类 ps.Commands.Clear()
,使用静态 .AddScript()
实例
并在其静态构造函数中添加一次脚本块。
- 注意:考虑改用 instance 属性并使类实现
DataRulesPSScripts
以允许类的用户控制 PowerShell 实例的生命周期。
PowerShell
使用类的代码,通过管道传递参数:
IDisposable
示例调用(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
这是脚本块输出的第二个自定义对象的字符串表示。
[1]在PowerShell脚本代码中,相比之下,Console.WriteLine(
obj.GenerateRowKey(
new Dictionary<string,EntityProperty> { ["ParamA"] = new EntityProperty { Value = "bar" },["ParamB"] = new EntityProperty { Value = "baz" } }
).Result
);
实例被直接使用,通常以{{3}的形式} literals (@{Row=demo.EntityProperty; Key=2}
),使用 ScriptBlock
调用,script-block。
[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
为空。