Могу ли я отправлять команды потерянному процессу командной строки без окна? - PullRequest
0 голосов
/ 10 марта 2020

Я немного поиграл с созданием GUI для автоматизации сервера командной строки (хорошо, это Minecraft BDS). Пока я проводил некоторое тестирование, я случайно (более одного раза) остановил отладчик до того, как сервер был должным образом выключен, что оставило процесс исполняемого файла «осиротевшим». Я даже не смог найти его в диспетчере задач, поэтому я прибегнул к добавлению проверки в своем коде для любых уже запущенных экземпляров bedrock_server.exe, а затем просто убил ее. Однако я бы хотел бы , чтобы позволить программному обеспечению сервера завершать работу "нормально", а не просто завершать процесс.

Мой вопрос в том, что командное окно не отображается и больше не «привязан» к экземпляру моего приложения, есть ли способ отправить ему команду stop, чтобы она могла корректно завершить работу?

Я пытался использовать StandardInput, но поскольку текущий экземпляр моего приложения не является экземпляром, который фактически запустил процесс, он не может записать в StandardInput потерянного процесса:

myProc.StandardInput.WriteLine("stop")

приводит к исключению, System.InvalidOperationException: 'StandardIn has not been redirected.'

Я думал об использовании SendKeys:

AppActivate(myProc.Id)
My.Computer.Keyboard.SendKeys("stop")

, но AppActivate не удается, когда я пытаюсь AppActivate процесс с исключением, System.ArgumentException: 'Process '[Id]' was not found.'

Использование myProc.Kill() действительно завершает процесс, мне просто интересно, может ли быть менее «ядерный» вариант.

Другая причина, по которой я хотел бы это выяснить, заключается в том, что я хотел бы , чтобы иметь возможность повторно использовать существующий / работающий процесс сервера, а не убивать его. Однако, если я не могу понять, как подключиться к этому процессу для выдачи команд, мой единственный вариант (на данный момент) - завершить процесс и начать заново с экземпляром fre sh.


МОЙ КОД

Для справки, код, который я использую для запуска сервера:

'------------------------------
'PUBLIC VARIABLE DECLARATION LOCATED IN ANOTHER FILE
Public BDS_PROC As Process
'------------------------------

        Dim ServerStartInfo As New ProcessStartInfo

        With ServerStartInfo
            .FileName = <PATH TO bedrock_server.exe>
            .UseShellExecute = False
            .WindowStyle = ProcessWindowStyle.Hidden
            .CreateNoWindow = True
            .RedirectStandardError = True
            .RedirectStandardOutput = True
            .RedirectStandardInput = True
        End With

        BDS_PROC = New Process

        With BDS_PROC
            .StartInfo = ServerStartInfo
            .EnableRaisingEvents = True
            AddHandler .OutputDataReceived, AddressOf BDSMessageReceived
            AddHandler .ErrorDataReceived, AddressOf BDSErrorReceived
            .Start()
            .BeginErrorReadLine()
            .BeginOutputReadLine()
        End With

и полная проверка текущих "потерянных" запущенных процессов (с моими неудачными попытками для отправки закомментированной команды stop:

    Public Function IsInstanceRunning() As Boolean
        Dim IsRunning As Boolean = True
        Dim Proc As Process() = Process.GetProcessesByName("bedrock_server")

        If Proc.Count > 0 Then
            If MessageBox.Show("The server is already running [" & Proc.Count.ToString & " instance(s)]." & vbCrLf & vbCrLf &
                               "Do you want to stop the currently running server?",
                               "SERVER RUNNING",
                               MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) = DialogResult.Yes Then
                Try
                    For p As Integer = 0 To Proc.Count - 1
                        'Proc(p).StandardInput.WriteLine("stop")
                        Proc(p).Kill()
                    Next p

                    IsRunning = False
                Catch ex As Exception
                    'Try
                    '    AppActivate(Proc(0).Id)
                    '    My.Computer.Keyboard.SendKeys("stop")
                    'Catch ex2 As Exception

                    'End Try
                    MessageBox.Show(ex.Message)
                End Try
            End If
        End If

        Return IsRunning
    End Function

РЕДАКТИРОВАТЬ:

Я нашел предложение с кодом из вопроса на C# Угол и попытался принять эту реализацию для моих целей. Я не получил никаких исключений или ошибок, но, к сожалению, он не остановил сервер. Вот этот код для справки, на случай, если он кому-то поможет:

Imports System.Runtime.InteropServices
Imports System.Text

Module PUBLIC
    <DllImport("kernel32.dll")>
    Private Function GetConsoleTitle(ByVal lpConsoleTitle As StringBuilder, ByVal nSize As UInteger) As UInteger
    End Function

    <DllImport("user32.dll")>
    Private Function FindWindow(ByVal ZeroOnly As IntPtr, ByVal lpWindowName As String) As IntPtr
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Private Function AttachConsole(ByVal dwProcessId As UInteger) As Boolean
    End Function

    <DllImport("kernel32")>
    Private Function FreeConsole() As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Function SetForegroundWindow(ByVal hWnd As IntPtr) As Boolean
    End Function

    Public Function IsInstanceRunning() As Boolean
        Dim IsRunning As Boolean = True
        Dim Proc As Process() = Process.GetProcessesByName("bedrock_server")

        If Proc.Count > 0 Then
            If MessageBox.Show("The server is already running [" & Proc.Count.ToString & " instance(s)]." & vbCrLf & vbCrLf &
                               "Do you want to stop the currently running server?",
                               "SERVER RUNNING",
                               MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) = DialogResult.Yes Then
                Try
                    For p As Integer = 0 To Proc.Count - 1
                        FreeConsole()
                        Dim ok As Boolean = AttachConsole(CUInt(Proc(p).Id))

                        If Not ok Then
                            Dim [error] As Integer = Marshal.GetLastWin32Error()
                            MessageBox.Show([error].ToString)
                        Else
                            Dim sb As StringBuilder = New StringBuilder(256)
                            GetConsoleTitle(sb, 256UI)
                            Dim hWnd As IntPtr = FindWindow(IntPtr.Zero, sb.ToString())
                            SetForegroundWindow(hWnd)
                            SendKeys.SendWait("stop{ENTER}")
                        End If
                    Next p

                    IsRunning = False
                Catch ex As Exception
                    MessageBox.Show(ex.Message)
                End Try
            End If
        End If

        Return IsRunning
    End Function
End Module

Проблема, с которой я сталкиваюсь, заключается в том, что, поскольку консольное приложение было запущено без окна, нет ничего на самом деле для меня, чтобы подключиться с этим кодом. Я все еще вынужден Kill() процесс.

...