如何在我的C# WinForms应用程序中运行简短的powershell脚本?

qxsslcnc  于 2022-11-16  发布在  C#
关注(0)|答案(1)|浏览(206)

我构建了一个监控应用程序,它将监控网络中几台不同服务器上的几项服务,我希望显示这些服务的状态以及它们是否仍在工作。
假设我想运行这个简短的Powershell脚本;

Get-Service -ComputerName "DESKTOP" -Name "WinDefend"

假设我想使用Timer事件每隔1分钟运行一次。

private void InitializeTimer()
{
    // Call this procedure when the application starts 
    timer1.Interval = 60000; // 60 seconds
    timer1.Tick += new EventHandler(timer1_Tick);

    // Enable timer
    timer1.Enabled = true;
}

// Timer tick
private void timer1_Tick(object sender, EventArgs e)
{
    // Powershell script here
}

我如何在这个例子中实现这个简短的Powershell脚本呢?
我的第二个问题也是,如何在检索数据后正确显示数据?我是否可以将数据写入文本框标签
非常感谢您的反馈和/或帮助!

5tmbdcev

5tmbdcev1#

C# WinForms调用PowerShell 5.1

要克服的问题:

1.要在C#中运行PowerShell 5.1版,我相信您的C#程序需要基于.NET Framework,可能是4.x。这可能不是真的,但我已经在这方面花费了比我预期的更多的时间,所以不打算研究替代方案。

  1. C#曾经通过Nuget提供一种将PowerShell 5.1添加到项目中的简单方法。现在情况不再如此,您现在必须采取额外的步骤来获取为PowerShell 5.1设计的System.Management.Automation.dll版本。
    1.虽然您可以通过额外的步骤从NuGet获取System.Management.Automation.dll,但它并不像完全更新的Windows 10系统上的PowerShell 5.1所使用的版本那么新。
  2. PowerShell 7.x版本提供的Get-Service版本,我相信6.x版本也一样,提供ComputerName参数,也不提供任何访问远程计算机的内置方法。有在远程系统上执行Get-Service并返回结果的选项,但这也可能是有问题的-如防火墙问题、某些服务(s)未运行,我认为某些注册表项设置可能会导致问题等...
  3. Forms.Timer类别可以更新表单上的控件,因为它使用相同的执行绪,BUT,这表示Get-Service从远端计算机传回信息的等待时间为20秒,将会锁定该执行绪并冻结表单。

使用正确版本的System.Management.Automation.dll生成VS C#项目:

一些通用步骤:

Start Visual Studio 2022:
    "Create a new project"
    C# -> Windows -> Desktop
    Windows Forms App (.NET Framework) -> Next
        Set "Project name" to project's name
        Set "Location" to project's path
        Set "Framework" to ".NET Framework 4.8"
        "Create"
Working in the Visual Studio 2022 Project:
    Tools -> NuGet Package Manager -> Package Manager Console
    Enter command to add System.Management.Automation.dll
        Get command from here: https://www.nuget.org/packages/System.Management.Automation.dll/
        NuGet command: NuGet\Install-Package System.Management.Automation.dll -Version 10.0.10586
    Optional:
        In PowerShell 5.1
            Navigate to the project's \packages\System.Management.Automation.dll.10.0.10586.0\lib\net40
            Execute: Copy ([PSObject].Assembly.Location)

从Windows的PowerShell 5.1复制System.Management.Automation.dll的诀窍来自这里(同一页上的其他有用信息):https://stackoverflow.com/a/13485939/4190564

在设计窗体和控件时,我相信这是我使用的所有唯一设置:

Form:
    (Name) = runPowerShellForm
    Size = 700, 200
    Text = "Test Form for Running PowerShell"
    {Set Load to run RunPS}
Label:
    (Name) = outputLabel
    AutoSize = false
    Anchor = Top, Bottom, Left, Right
    BackColor = White
    Font = Lucida Console, 12pt
    ForeColor = Navy
    Size = 660, 114
    Text = ""
TextBox:
    (Name) = computerNameTextBox
    Anchor = Bottom, Left, Right
    Size = 532, 20
Button:
    (Name) = updateComputerButton
    Anchor = Bottom, Right
    Size = 122, 23
    Text = "Update Computer"
    {Set button click}

这是真正的代码

public partial class runPowerShellForm : Form {
        private static string _computerName = ".";
        private static int _tickCount = 0;
        private static System.Timers.Timer _timer = new System.Timers.Timer();
        private static Label _outputLabel = null;
        private static PowerShell ps = PowerShell.Create();

        private static void NewGetService(string computerName) {
            _computerName = computerName;
            ps = PowerShell.Create();
            ps.AddCommand("Get-Service").AddParameter("ComputerName", computerName).AddParameter("Name", "WinDefend");
        }

        private static void RunPS() {
            string LabelText = "Computer: " + _computerName + "\r\nTick Count: " + (++_tickCount).ToString() + "\r\n\r\n";
            LabelText += "Status   Name               DisplayName\r\n";
            LabelText += "------   ----               -----------\r\n";
            foreach(PSObject result in ps.Invoke()) {
                LabelText += String.Format(
                    "{0,-9}{1,-19}{2}",
                    result.Members["Status"].Value,
                    result.Members["Name"].Value,
                    result.Members["DisplayName"].Value);
            }
            _outputLabel.BeginInvoke(new UpdateLabel(UpdateMethod), _outputLabel, LabelText);
        }

        public delegate void UpdateLabel(Label arg1, string arg2);

        public static void UpdateMethod(Label labelCtrl, string textStr) {
            labelCtrl.Text = textStr;
        }

        private static void OnTickEvent(Object source, System.Timers.ElapsedEventArgs e) {
            RunPS();
        }

        public runPowerShellForm() {
            InitializeComponent();
        }

        private void updateComputerButton_Click(object sender, EventArgs e) {
            NewGetService(this.computerNameTextBox.Text);
            this.computerNameTextBox.Text = "";
            RunPS();
        }

        private void runPowerShellForm_Load(object sender, EventArgs e) {
            _outputLabel = this.outputLabel;
            _timer.Elapsed += OnTickEvent;
            _timer.Interval = 30000;
            _timer.Enabled = true;
            NewGetService(_computerName);
            RunPS();
        }

        private void runPowerShellForm_SizeChanged(object sender, EventArgs e) {
            this.computerNameTextBox.Text = this.Size.ToString();
        }
    }

最初的答案:

这福尔斯没有回答问题,但它确实说明了如何从PowerShell调用的C#中调用PowerShell。

如果您尝试执行单个PowerShell命令,则Run PSCmdLets in C# code (Citrix XenDesktop)How to execute a powershell script using c# and setting execution policy?的答案可能会满足您的要求。
我现在在PowerShell中执行所有操作,因此创建一个调用调用PowerShell的C#的PowerShell脚本会更容易。此PowerShell脚本调用C#类DoPSRun方法,该方法调用Get-Service -ComputerName "." -Name "WinDefend",然后使用WriteLine语句模拟Get-Service的预期输出:

Add-Type -Language 'CSharp' -TypeDefinition @'
using System;
using System.Management.Automation;
public class DoPS {
    public void Run(){
        //Get-Service -ComputerName "." -Name "WinDefend"
        PowerShell ps = PowerShell.Create();
        ps.AddCommand("Get-Service").AddParameter("ComputerName", ".").AddParameter("Name", "WinDefend");
        Console.WriteLine("Status   Name               DisplayName");
        Console.WriteLine("------   ----               -----------");
        foreach (PSObject result in ps.Invoke()) {
            Console.WriteLine(
                "{0,-9}{1,-19}{2}",
                result.Members["Status"].Value,
                result.Members["Name"].Value,
                result.Members["DisplayName"].Value);
        }
    }
}
'@
$DoPS = [DoPS]::new()
$DoPS.Run()

输出以下文本:

Status   Name               DisplayName
------   ----               -----------
Running  WinDefend          Microsoft Defender Antivirus Service

相关问题