从C#调用PowerShell的另一种解决方案

本文介绍了如何在C#中简化PowerShell的使用,提供了一个无需深入研究环境的简单方法。文章包括先决条件、自定义扩展库方法、使用代码示例以及错误处理。通过创建一个PowerShell处理程序类,可以方便地执行命令和脚本,同时处理参数和错误反馈。
摘要由CSDN通过智能技术生成

目录

介绍

先决条件

自定义扩展库中的方法要求

使用代码

在PowerShell环境中执行命令

如何使用以这种方式准备的类

C#下的PowerShell处理程序类的完整代码


介绍

C#PowerShell支持有点麻烦。在这里,我提出了一个简单的解决方案,而无需深入研究环境。本文旨在成为有关使用从C#编写的程序调用的PowerShell来支持各种Microsoft环境的循环的基础。

先决条件

所有示例均在Windows 10 Pro 21H2 19044.1387环境中的MS Visual Studio Community 2019中创建、编译和测试。

为了正确编译,需要安装NuGet包:Microsoft.PowerShell.3.ReferenceAssemblies

自定义扩展库中的方法要求

您可以使用已在各自的命名空间中定义的方法和类,但我提供了一些简单的解决方案示例,以便您理解和清楚。您当然可以以不同的方式编写它们(以更简单或更清晰的方式),但我们不要过分投入。

以下是当string值为空或仅包含空格时返回true的方法:

public static bool IsNullEmptyOrWhite(this string sValue) {...}

Name/Value形式定义PowerShell参数的类。它可以替换为任何方便的dictionary类:

public class ParameterPair
{
    public string Name { get; set; } = string.Empty;
    public object Value { get; set; } = null;
}

使用代码

PowerShell环境中执行命令

当然,有很多方法可以打开PowerShell、在其中发出命令并下载其结果。我把自己限制在两个:

  • 调用一个脚本,其中所有命令及其数据必须以一行文本的形式呈现
  • Cmdlet调用,我们一次只能给出一个命令,它的参数作为Name/Value对传递,其中值可以是任何类型

为此,RunPS 定义了两种具有不同参数集的方法,经过适当的参数处理后,使用统一的ExecutePS方法。

/// <summary>
/// Basic method of calling PowerShell a script where all commands 
/// and their data must be presented as one line of text
/// </summary>
/// <param name="ps">PowerShell environment</param>
/// <param name="psCommand">A single line of text 
/// containing commands and their parameters (in text format)</param>
/// <param name="outs">A collection of objects that contains the feedback</param>
/// <returns>The method returns true when executed correctly 
/// and false when some errors have occurred</returns>
public static bool RunPS(PowerShell ps, string psCommand, out Collection<PSObject> outs)
{
    //Programmer's Commandment I: Remember to reset your variables
    outs = new Collection<PSObject>();
    HasError = false;
    
    //Cleanup of PowerShell also due to commandment I
    ps.Commands.Clear();
    ps.Streams.ClearStreams();
    
    //We put the script into the PowerShell environment 
    //along with all commands and their parameters
    ps.AddScript(psCommand);
    
    //We are trying to execute our command
    outs = ExecutePS(ps);
    
    //The method returns true when executed correctly and false 
    //when some errors have occurred
    return !HasError;
}

/// <summary>
/// Method 2 cmdlet call where we can only give one command at a time
/// and its parameters are passed as Name/Value pairs,
/// where values can be of any type
/// </summary>
/// <param name="ps">PowerShell environment</param>
/// <param name="psCommand">Single command with no parameters</param>
/// <param name="outs">A collection of objects that contains the feedback</param>
/// <param name="parameters">A collection of parameter pairs
/// in the form Name/Value</param>
/// <returns>The method returns true when executed correctly
/// and false when some errors have occurred</returns>
public static bool RunPS(PowerShell ps, string psCommand,
       out Collection<PSObject> outs, params ParameterPair[] parameters)
{
    //Programmer's Commandment I: Remember to reset your variables
    outs = new Collection<PSObject>();
    HasError = false;        
    
    if (!psCommand.Contains(' '))
    {
        //Cleanup of PowerShell also due to commandment I
        ps.Commands.Clear();
        ps.Streams.ClearStreams();
        
        //We put a single command into the PowerShell environment
        ps.AddCommand(psCommand);
        
        //Now we enter the command parameters in the form of Name/Value pairs
        foreach (ParameterPair PP in parameters)
        {
            if (PP.Name.IsNullEmptyOrWhite())
            {
                LastException = new Exception("E1008:Parameter cannot be unnamed");
                return false;
            }
            if (PP.Value == null) ps.AddParameter(PP.Name);
            else ps.AddParameter(PP.Name, PP.Value);
        }
        
        //We are trying to execute our command
        outs = ExecutePS(ps);
    }
    //And here we have a special exception 
    //if we tried to apply the method not to a single command
    else LastException = new Exception
    ("E1007:Only one command with no parameters is allowed");
    
    //The method returns true when executed correctly 
    //and false when some errors have occurred
    return !HasError;
}

/// <summary>
/// Internal method in which we try to execute a script or command with parameters
/// This method does not need to return a fixed value 
/// that indicates whether or not the execution succeeded,
/// since the parent methods use the principal properties of the class set in it.
/// </summary>
/// <param name="ps">PowerShell environment</param>
/// <returns>A collection of objects that contains the feedback</returns>
private static Collection<PSObject> ExecutePS(PowerShell ps)
{
    Collection<PSObject> retVal = new Collection<PSObject>();
    
    //We are trying to execute our script
    try
    {
        retVal = ps.Invoke();
        
        // ps.HadErrors !!! NO!
        // The PowerShell environment has a special property that
        // indicates in the assumption whether errors have occurred
        // unfortunately, most often I have found that despite errors,
        // its value is false or vice versa,
        // in the absence of errors, it pointed to the truth.
        
        //Therefore, we check the fact that errors have occurred,
        //using the error counter in PowerShell.Streams
        if (ps.Streams.Error.Count > 0) //czy są błędy wykonania
        {
            //We create another general exception, but we do not raise it.
            LastException = new Exception("E0002:Errors were detected during execution");
            
            //And we write runtime errors to the LastErrors collection
            LastErrors = new PSDataCollection<ErrorRecord>(ps.Streams.Error);
        }
    }
    
    //We catch script execution errors and exceptions
    catch (Exception ex)
    {
        //And if they do, we create a new general exception but don't raise it
        LastException = new Exception("E0001:" + ex.Message);
    }
    
    //Returns a collection of results
    return retVal;
}

如何使用以这种方式准备的类

一开始,我们为结果创建一个空集合:

Collection<PSObject> Results = new Collection<PSObject>();

然后我们需要打开单独的PowerShell环境:

PowerShell ps = PowerShell.Create();

然后尝试执行脚本:

if (PS.Basic.RunPS(ps, "Get-Service | 
    Where-Object {$_.canpauseandcontinue -eq \"True\"}", out Results))
{ Interpreting the results… }
else
{ Interpretation of errors… }

或带参数的命令:

if (PS.Basic.RunPS(ps, "Get-Service", out Results,
   new ParameterPair { Name = "Name", Value = "Spooler"}
))
{ Interpreting the results… }
else
{ Interpretation of errors… }

下一步是解释结果:

在每个例子中,我们都会回顾Collection<PSObject> Results集合。

例如,对于脚本"Get-Service ...",集合由基类型(Results [0] .BaseObject.GetType()) ServiceController,其中,具有属性name”)的对象组成。我们可以用Results [0] .Properties ["name"]. Value来读。

对结果的解释归结为查看Results并检索我们感兴趣的适当属性的值。

或者错误的解释:

当尝试执行PowerShell命令时发生错误时,基类有几个属性和变量要处理。

错误准备执行命令导致的错误

这些错误可能来自执行命令之前不正确地准备Powershell。例如,我们会忘记通过传递一个空的ps来打开这个环境。或者,我们将传递一些语法废话,而不是正确的脚本/命令。

在这种情况下,当尝试调用ps.Invoke ();时,您可以在描述中找到错误原因的异常。在基类中,LastException变量随后被定义为带有消息"E0001:" + ex.Message(即代码E0001前面的异常描述)。

在错误解释阶段,您可以通过检查ErrorCode属性(PS.Basic.ErrorCode == 1
的值来检查是否发生了此类错误,然后使用LastException.Message描述进行更详细的错误处理。

命令执行错误

在执行完全有效的命令或脚本时,我们也会遇到错误。例如,当我们指定一个对象的Identity时,其值在PowerShell环境的可见性范围内没有任何对象。然后我们会得到未找到错误名称的错误,但只是尝试执行该命令不会导致异常。

我们可以在集合PSDataCollection<ErrorRecord> LastErrors中找到这样的错误。

在给出的类中引入的错误处理模型将在LastException中导致一个新的异常,其描述形式为: “E0002: Errors were detected during execution。通过"ErrorCode" (PS.Basic.ErrorCode == 2) 检查后,我们可以从集合中读取后续错误,并根据LastErrors [0] .Exception.Message中的异常描述确定其原因。与LastException一样,现在在此基础上,需要进行更详细的错误处理。

C#下的PowerShell处理程序类的完整代码

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;

namespace PS
{
    public static class Basic
    {
        /// <summary>
        /// The last exception that occurred in the PS.Basic class
        /// </summary>
        public static Exception LastException = null;

        /// <summary>
        /// Collection of PowerShell runtime errors
        /// </summary>
        public static PSDataCollection<ErrorRecord> LastErrors = 
                                                    new PSDataCollection<ErrorRecord>();

        /// <summary>
        /// Auxiliary Property that helps to check if there was an error and 
        /// resets the error state
        /// </summary>
        public static bool HasError
        {
            get
            {
                return LastException != null;
            }
            set
            {
                if(!value)
                {
                    LastException = null;
                    LastErrors = new PSDataCollection<ErrorRecord>();
                }
            }
        }

        /// <summary>
        /// A helper Property to help you get the error code
        /// </summary>
        public static int ErrorCode
        {
            get
            {
                if (HasError) return int.Parse(LastException.Message.Substring(1, 4));
                return 0;
            }
        }

        /// <summary>
        /// Basic method of calling PowerShell a script where all commands 
        /// and their data must be presented as one line of text
        /// </summary>
        /// <param name="ps">PowerShell environment</param>
        /// <param name="psCommand">A single line of text containing commands 
        /// and their parameters (in text format)</param>
        /// <param name="outs">A collection of objects that contains the feedback</param>
        /// <returns>The method returns true when executed correctly 
        /// and false when some errors have occurred</returns>
        public static bool RunPS
               (PowerShell ps, string psCommand, out Collection<PSObject> outs)
        {
            //Programmer's Commandment I: Remember to reset your variables
            outs = new Collection<PSObject>();
            HasError = false;

            //Cleanup of PowerShell also due to commandment I
            ps.Commands.Clear();
            ps.Streams.ClearStreams();

            //We put the script into the PowerShell environment 
            //along with all commands and their parameters
            ps.AddScript(psCommand);

            //We are trying to execute our command
            outs = ExecutePS(ps);

            //The method returns true when executed correctly and false 
            //when some errors have occurred
            return !HasError;
        }

        /// <summary>
        /// Method 2 cmdlet call where we can only give one command 
        /// at a time and its parameters are passed as Name/Value pairs,
        /// where values can be of any type
        /// </summary>
        /// <param name="ps">PowerShell environment</param>
        /// <param name="psCommand">Single command with no parameters</param>
        /// <param name="outs">A collection of objects that contains the feedback</param>
        /// <param name="parameters">A collection of parameter pairs 
        /// in the form Name/Value</param>
        /// <returns>The method returns true when executed correctly 
        /// and false when some errors have occurred</returns>
        public static bool RunPS(PowerShell ps, string psCommand, 
               out Collection<PSObject> outs, params ParameterPair[] parameters)
        {
            //Programmer's Commandment I: Remember to reset your variables
            outs = new Collection<PSObject>();
            HasError = false;
           
            if (!psCommand.Contains(' '))
            {
                //Cleanup of PowerShell also due to commandment I
                ps.Commands.Clear();
                ps.Streams.ClearStreams();

                //We put a single command into the PowerShell environment
                ps.AddCommand(psCommand);

                //Now we enter the command parameters in the form of Name/Value pairs
                foreach (ParameterPair PP in parameters)
                {
                    if (PP.Name.IsNullEmptyOrWhite())
                    {
                        LastException = new Exception("E1008:Parameter cannot be unnamed");
                        return false;
                    }

                    if (PP.Value == null) ps.AddParameter(PP.Name);
                    else ps.AddParameter(PP.Name, PP.Value);
                }

                //We are trying to execute our command
                outs = ExecutePS(ps);
            }
            //And here we have a special exception if we tried 
            //to apply the method not to a single command
            else LastException = new Exception("E1007:Only one command 
                                                with no parameters is allowed");

            //The method returns true when executed correctly and false 
            //when some errors have occurred
            return !HasError;
        }

        /// <summary>
        /// Internal method in which we try to execute a script or command with parameters
        /// This method does not need to return a fixed value that indicates 
        /// whether or not the execution succeeded,
        /// since the parent methods use the principal properties of the class set in it.
        /// </summary>
        /// <param name="ps">PowerShell environment</param>
        /// <returns>A collection of objects that contains the feedback</returns>
        private static Collection<PSObject> ExecutePS(PowerShell ps)
        {
            Collection<PSObject> retVal = new Collection<PSObject>();

            //We are trying to execute our script
            try
            {
                retVal = ps.Invoke();

                // ps.HadErrors !!! NO!
                // The PowerShell environment has a special property 
                // that indicates in the assumption whether errors have occurred
                // unfortunately, most often, I have found that despite errors 
                // its value is false or vice versa,
                // in the absence of errors, it pointed to the truth.

                // Therefore, we check the fact that errors have occurred, 
                // using the error counter in PowerShell.Streams
                if (ps.Streams.Error.Count > 0) //czy są błędy wykonania
                {
                    //We create another general exception, but we do not raise it.
                    LastException = new Exception
                                    ("E0002:Errors were detected during execution");

                    //And we write runtime errors to the LastErrors collection
                    LastErrors = new PSDataCollection<ErrorRecord>(ps.Streams.Error);
                }
            }
            //We catch script execution errors and exceptions
            catch (Exception ex)
            {
                //And if they do, we create a new general exception but don't raise it
                LastException = new Exception("E0001:" + ex.Message);
            }

            //Returns a collection of results
            return retVal;
        }
    }

    /// <summary>
    /// Class defining the PowerShell parameter in the form Name/Value.
    /// it can be replaced with any convenient dictionary class
    /// </summary>
    public class ParameterPair
    {
        public string Name { get; set; } = string.Empty;

        public object Value { get; set; } = null;
    }
}

https://www.codeproject.com/Articles/5318610/One-More-Solution-to-Calling-PowerShell-from-Cshar

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值