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

时间:2013-01-13 19:40:11

标签: c# powershell process cancellation

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

  1. 启动远程进程
  2. 终止他们
  3. 捕获远程进程的标准和错误输出(不是在进程终止之后,而是在生成该输出时)。
  4. 捕获远程进程的退出代码。
  5. 我正在使用以下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然后从不同的运行空间运行“Stop Process”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,告知执行远程计算机上的脚本(invoke-command)。 Powershell默认捕获远程PC上产生的输出,因此我可以很容易地获得Process实例的这个。当我想取消时,我杀死了本地进程。远程命令的返回代码比较棘手。我会在一段时间内发布命令,脚本和C#-snippet。

2 个答案:

答案 0 :(得分: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)

答案 1 :(得分: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以获取退出代码,如果在输出中也是如此。