Отменяемые удаленные процессы и захват выходных данных

Я пытаюсь использовать PowerShell через 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 в некоторых процессах? способ, и получу ли я прокси для процесса?) Если нет, то как это сделать? Имейте в виду, что мне нужен вывод по мере его создания, а не после завершения процесса.

Это не фиксирует завершение процесса. Я попытался выполнить завершение, сначала захватив идентификатор процесса, а затем запустив сценарий PowerShell «Остановить процесс» из другого пространства выполнения. Это не удалось, потому что «конвейер уже запущен», а конвейеры не могут работать параллельно... Затем я попытался вызвать process.Kill() из C#, это сработало для моего локального процесса, но 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 по умолчанию фиксирует выходные данные, созданные на удаленном ПК, поэтому я мог легко получить это из экземпляра процесса. Когда я хотел отменить, я убил локальный процесс. Код возврата удаленной команды был сложнее. Через некоторое время я опубликую команду, скрипт и C#-фрагмент.


person Mikhail    schedule 13.01.2013    source источник
comment
Этот первый фрагмент powershell не запустит удаленный процесс. Он запускает процесс локально. Используете ли вы задания или удаленное взаимодействие для его выполнения?   -  person Mike Zboray    schedule 14.01.2013
comment
@mikez Я использую удаленное взаимодействие, создавая удаленное пространство выполнения (хотя это не показано явно, я говорю это только в комментариях). Насколько я понял, этот фрагмент действительно работает на удаленной машине.   -  person Mikhail    schedule 14.01.2013
comment
Ознакомьтесь с библиотекой MedallionShell, которая значительно упрощает обработку потоков ввода-вывода. С MedallionShell вы также можете легко и безопасно завершить любой процесс с помощью метода Kill().   -  person ChaseMedallion    schedule 29.08.2014


Ответы (2)


Задания Powershell можно выполнять удаленно, они будут собирать их выходные данные и возвращать их на исходный компьютер. При этом вам не нужно будет создавать удаленное пространство выполнения на С#, только локальное.

Сценарий 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

Вам, вероятно, просто нужно настроить параметр блока сценария на invoke-command, чтобы получить код выхода, если вы хотите, чтобы он также был на выходе.

person Mike Zboray    schedule 13.01.2013
comment
Попробую это, спасибо. Я планирую выполнять сотни процессов на сотнях компьютеров в любой момент времени. Возможно ли управлять ими всеми из локального пространства выполнения (как задания) в PowerShell? Кажется, он жалуется, даже если я создаю второй конвейер и пытаюсь его использовать, в то время как первый, который я создал, не закрыт. (Я новичок в PowerShell и, похоже, здесь отсутствует какая-то основная концепция.) - person Mikhail; 14.01.2013
comment
Я ожидаю, что это возможно. Параметр имя_компьютера на самом деле принимает массив имен, поэтому вы можете сделать это так: invoke-command -computername $computernames -scriptblock { start-process "C:\Echo.exe" -wait } -asjob - person Mike Zboray; 14.01.2013
comment
Я понимаю. Это не будет одна команда для всех ПК, команды будут разными и не связанными друг с другом. - person Mikhail; 14.01.2013
comment
Не удалось получить какие-либо выходные данные с удаленного компьютера. Этот скрипт ничего не печатал (даже в консоли PS), кроме информации о задании (имя, статус и т.д.). Я думаю о переходе на использование командной строки winrs, запущенной из приложения .Net, как процесса. Вывод будет перенаправлен автоматически, а завершение этого процесса будет означать отмену команды. - person Mikhail; 14.01.2013
comment
Майк, спасибо за вашу помощь в этом! Я обновил вопрос с идеей, которую я принял в конце. Подробности выложу завтра, если интересно. - person Mikhail; 18.01.2013

Из документов по объекту Process это позволит вам запускать и останавливать только локальные процессы. Поэтому, если вы хотите запускать и останавливать удаленные процессы, вам нужно использовать либо удаленное взаимодействие PowerShell, либо такой инструмент, как psexec. Я вижу, что ваши комментарии C# указывают на то, что вы настроите удаленное пространство выполнения. Это прекрасно работает, если у вас есть PowerShell v2 на каждом удаленном компьютере, включено удаленное взаимодействие, предпочтительно вы находитесь в домене и имеете соответствующие учетные данные администратора. IIRC процесс С#, настраивающий удаленное пространство выполнения, должен запускаться от имени администратора, а используемые им учетные данные должны иметь права администратора на удаленном компьютере (если вы не настроили специальные конечные точки удаленного взаимодействия на каждом из удаленных компьютеров).

Если у вас есть PowerShell v2 или v3 на каждом удаленном компьютере и вы можете включить удаленное взаимодействие (просто запустите Enable-PSRemoting -force), то вы легко сможете делать то, что хотите. Однако нет необходимости раскрываться, чтобы использовать объект .NET Process. Просто используйте командлеты PowerShell. Поместите что-то подобное в скрипт, добавьте его в созданный вами конвейер и вызовите его.

$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)
person Keith Hill    schedule 13.01.2013
comment
Кит, спасибо за ответ. Я опробую командлеты в ближайшие 24 часа. Любые идеи по асинхронному (то есть: без ожидания завершения процесса) захвату вывода процесса из кода С#? Что касается отмены, я не имел в виду сценарий тайм-аута, хотя это может быть полезно, спасибо, что указали на это. - person Mikhail; 14.01.2013
comment
Это действительно запускает процесс, но мне не удалось его асинхронно остановить или получить стандартный вывод — он утонул в PowerShell .Net API. - person Mikhail; 14.01.2013
comment
Кит, спасибо за помощь! Я обновил вопрос с идеей, которую я принял в конце. Подробности выложу завтра, если интересно. - person Mikhail; 18.01.2013