可取消的远程进程和捕获输出

本文关键字:输出 进程 程进程 可取消 | 更新日期: 2023-09-27 17:52:57

我试图使用PowerShell通过它的c# API做以下事情:

  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是否以某种方式参与,我是否获得进程的代理?)如果不是,该如何做?请注意,我需要输出,因为它正在产生,而不是在过程终止后

不捕获进程终止。我尝试通过首先捕获进程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,因为它有分叉问题)。

UPDATE:我最后做的是调用(通过c#启动一个进程——"powershell"+"-Command…")到powershell.exe,它被告知在远程计算机上执行一个脚本(调用命令)。Powershell在默认情况下捕获远程PC上产生的输出,因此我可以很容易地从Process实例中获得它。当我想取消时,我杀死了本地进程。远程命令的返回代码更加复杂。我将在一段时间内发布命令,脚本和c#片段。

可取消的远程进程和捕获输出

Powershell作业可以远程执行,并将捕获它们的输出并将其返回到原始计算机。这样你就不需要在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,以便在输出中也获得退出代码。

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

如果你在每台远程计算机上都安装了PowerShell v2或v3,并且可以启用远程(只需运行enable - psremoting -force),那么你应该很容易就能做你想做的事情。但是,不需要下拉来使用. net Process对象。只需使用PowerShell cmd命令。把这样的东西放在一个脚本中,把它添加到你创建和调用的管道中。

$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)