当前 PowerShell 实例的状态对于 C# 中的此操作无效

问题描述

我有下面的方法,它被不同的 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); } } 的参数或参数,您就无法删除它们并指定不同的来执行另一个呼叫 - 据我所知。

解决方法是使用 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 为空。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...