1

我正在尝试通过其 C# API 使用 PowerShell 来执行以下操作:

  1. 启动远程进程
  2. 终止他们
  3. 捕获远程进程的标准和错误输出(不是在进程终止之后,而是在生成该输出时)。
  4. 捕获远程进程的退出代码。

我正在使用以下 PowerShell 脚本启动远程进程:

$ps = new-object System.Diagnostics.Process
$ps
$ps.StartInfo.Filename = "c:\Echo.exe"
$ps.StartInfo.Arguments = ""
$ps.StartInfo.RedirectStandardOutput = $true
$ps.StartInfo.RedirectStandardError = $true
$ps.StartInfo.UseShellExecute = $false
$ps.start()
$ps.WaitForExit()
$ps.ExitCode

我的原型的 C# 部分如下所示:

// Note: in this example the process is started locally
using (Runspace runspace = RunspaceFactory.CreateRunspace(/*_remotePcConnectionInfo*/))
{
    runspace.Open();

    Pipeline pipeline = runspace.CreatePipeline();
    // Scripts.RunExecutable is that script above
    pipeline.Commands.AddScript(Scripts.RunExecutable);

    pipeline.InvokeAsync();

    var process = (Process)pipeline.Output.Read().BaseObject;

    bool started = (bool)pipeline.Output.Read().BaseObject;

    // Not showing the dummy event handlers - they simply do a Console.WriteLine now.
    process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
    process.BeginOutputReadLine();

    // Not showing the dummy event handlers - they simply do a Console.WriteLine now.
    process.ErrorDataReceived += new DataReceivedEventHandler(process_ErrorDataReceived);
    process.BeginErrorReadLine();

    int processExitCode = (int) pipeline.Output.Read().BaseObject;
}

这确实捕获了本地运行的进程的输出,但这也适用于远程进程吗?(另一个不太重要的问题是:这如何适用于远程进程?.Net Remoting 是否以某种方式参与,我是否获得了 Process 的代理?)如果没有,有什么方法可以做到这一点?请注意,我需要输出,因为它正在生成,而不是在进程终止之后

这不会捕获进程终止。我已经尝试通过先捕获进程 ID 然后从不同的运行空间运行“停止进程”PowerShell 脚本来进行终止。那失败了,因为“管道已经在运行”并且管道无法并行运行......然后我尝试从 C# 调用 process.Kill(),这适用于我的本地进程,但 SO 报告它不适用于远程进程...然后我尝试调整我的 PowerShell 脚本,使其包含一个全局变量和一个等待循环,但我从来没有弄清楚如何在管道启动后设置该变量。添加的循环如下所示:

while ($ps.HasExited -eq $false -and $global:cancelled -eq $false)
{
    Start-Sleep -seconds 1
}

if ($global:cancelled -eq $true)
{
    $ps.Kill()
}

所以,那也失败了。关于我的场景的流程终止有什么建议吗?

PowerShell 甚至适合这个吗?(我们强烈反对我们之前尝试使用的 openSSH,因为它存在分叉问题)。

更新:我最终做的是调用(通过 C# 启动进程——“powershell”+“-Command ...”)到 powershell.exe,它被告知在远程执行脚本(调用命令)电脑。Powershell 默认捕获远程 PC 上产生的输出,因此我可以轻松获取 Process 实例的这个。当我想取消时,我杀死了本地进程。远程命令的返回码比较棘手。稍后我将发布命令、脚本和 C#-snippet。

4

2 回答 2

0

从 Process 对象的文档中,它只允许您启动和停止本地进程。因此,如果要启动和停止远程进程,则需要使用 PowerShell 远程处理或 psexec 之类的工具。我看到您的 C# 注释表明您将设置远程运行空间。如果您在每台远程计算机上都安装了 PowerShell v2,启用了远程处理,最好位于域上并拥有适当的管理员凭据,那么这将非常有用。IIRC 设置远程运行空间的 C# 进程必须以管理员身份运行,并且它使用的凭据必须在远程计算机上具有管理员权限(除非您在每台远程计算机上设置了特殊的远程端点)。

如果您在每台远程计算机上都有 PowerShell v2 或 v3 并且可以启用远程处理(只需运行 Enable-PSRemoting -force),那么您应该可以轻松地做您想做的事情。但是,无需下拉即可使用 .NET Process 对象。只需使用 PowerShell cmdlet。将这样的内容放入脚本中,将其添加到您创建的管道中并调用它。

$p = Start-Process Echo.exe -Arg "output from echo.exe" -PassThru
$p | Wait-Process -Timeout 3 # 3 seconds
if (!$?) { # Success code $? returns $false if Wait-Process times out
    $p | Stop-Process
}

对于异步支持,请尝试以下方式:

$script = {
    $pid
    ipconfig.exe /all
    $LastExitCode
}
$job = Invoke-Command localhost $script -AsJob
do {
    Receive-Job $job
}
while (!(Wait-Job $job -Timeout 1))
Receive-Job $job    
Remove-Job $job

使用返回的 $pid,您可以将其与 WMI 一起使用来查找所有子进程以在必要时杀死它们,如下所示:

$childPids = @(Get-WmiObject -Class Win32_Process -Filter "ParentProcessID=$PID" |Select-Object -Property ProcessID)
于 2013-01-13T21:15:31.320 回答
0

Powershell Jobs 可以远程执行,并将捕获其输出并将其返回到原始计算机。有了这个,您不必在 C# 中创建远程运行空间,只需创建本地运行空间。

执行此操作的 powershell 脚本如下所示:

$job = invoke-command -computername remotecomputer -scriptblock { start-process "C:\Echo.exe" -wait } -asjob
while ( $job.State -eq "Running" ) {
  wait-job -job $job -timeout 1
  receive-job -job $job -keep # print the current output but keep all output for later.
}

receive-job -job $job

如果在输出中也需要退出代码,您可能只需要调整 scriptblock 参数即可invoke-command获取退出代码。

于 2013-01-13T21:45:52.390 回答