Запуск пакетных файлов и чтение ПОСЛЕДНЕЙ строки вывода после паузы

У меня есть веб-приложение со страницей, полной пакетных файлов, которые пользователь может запускать, просматривать выходные данные и отправлять входные данные. Моя проблема возникает, когда процесс сталкивается с чем-то, что заставляет его приостанавливаться, например, с паузой или вопросом, который требует от пользователя нажатия Y или N для продолжения. Мы пойдем с паузой для целей этого вопроса.

Это мой командный файл:

pause

При запуске в Windows я получаю вывод, отображаемый на моем экране «Нажмите любую клавишу, чтобы продолжить ...», я нажимаю ввод, и он выходит. Но когда мое приложение запускает этот пакетный файл, я не получаю никакого вывода, но я знаю, чего он ждет, поэтому я нажимаю клавишу ввода, и только тогда я вижу вывод «Нажмите любую клавишу, чтобы продолжить…».

Я создал упрощенную версию своего кода в консольном приложении, и происходит то же самое, я получаю пустой экран, нажимаю клавишу ввода, а затем вижу "Нажмите любую клавишу, чтобы продолжить..."

Любая идея, как я могу получить эту строку вывода ДО того, как мне нужно будет нажать клавишу?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;

namespace BatchCaller
{
    class Program
    {
        static void Main(string[] args)
        {
            ProcessStartInfo psi = new ProcessStartInfo()
            {
                FileName = @"C:\Projects\BatchCaller\BatchCaller\Test2.bat",
                RedirectStandardOutput = true,
                RedirectStandardInput = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };

            Process proc = new Process();

            proc.StartInfo = psi;
            proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
            proc.Start();
            proc.BeginOutputReadLine();

            // Problem is not here, ignore this, just my temporary input method.
            // Problem still occurs when these two lines are removed.
            string inputText = Console.ReadLine();
            proc.StandardInput.WriteLine(inputText);

            proc.WaitForExit();
        }

        static void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            // This method doesnt get called for output: "Press any key to continue..."
            // Why?
            if (e.Data != null)
                Console.WriteLine(e.Data);
        }
    }
}

person Owen    schedule 16.11.2011    source источник
comment
Очень хороший вопрос. +1 за это.   -  person Kangkan    schedule 16.11.2011
comment
Я думаю, что запутал людей своим методом ввода temp. ИГНОРИРУЙТЕ ввод, это не проблема, если я удалю его из кода, я все равно не увижу вывод. Нажмите любую клавишу, чтобы продолжить.... Это проблема, которую я пытаюсь решить.   -  person Owen    schedule 16.11.2011
comment
Также удалите @echo off из пакетного файла. Это проясняет проблему, на экране отображается пауза, но нет. Нажмите любую клавишу, чтобы продолжить...   -  person Owen    schedule 16.11.2011
comment
После дальнейших исследований похоже, что событие proc_OutputDataReceived вызывается только тогда, когда пакетный файл переходит на новую строку, поэтому, когда он говорит, нажмите любую клавишу, чтобы продолжить..., курсор все еще находится на той же строке, поэтому мое приложение не отправляется этот текст.   -  person Owen    schedule 16.11.2011


Ответы (3)


Только что проверил и заметил, что пауза не возвращается к следующей строке текста, пока вы не нажмете клавишу. Вероятно, поэтому он не отображается, так как ваш код ищет возврат строки. Вместо использования ReadLine() попробуйте посмотреть, есть ли функция, которая будет отображать все символы по мере их вывода. (^ это должно решить вашу проблему, предоставляя вам более реальное представление о том, что происходит внутри)

person user980058    schedule 16.11.2011
comment
Не решение к сожалению. Все мое основное приложение выполняется асинхронно и продолжает выводить данные на экран до тех пор, пока их не останется, и в этом случае у него не осталось данных для вывода, пока я не нажму клавишу ввода. Если бы я заменил Console.ReadLine() на randomText, это не имело бы никакого значения. Все, что я делаю, это нажатие любой клавиши для продолжения. ReadLine() ищет пользовательский ввод, а не вывод процесса, что является проблемой здесь. - person Owen; 16.11.2011

Я бы подумал, что вам нужно переместить:

string inputText = Console.ReadLine();             
proc.StandardInput.WriteLine(inputText); 

в обработчик OutputDataReceived.

Затем в вашей основной вызовите proc.WaitForExit() и, если повезет (я не проверял это), должно произойти следующее:

  • выходной буфер proc очищается
  • ваш обработчик OutputDataReceived выполняется
  • Console.Write с помощью стандартного вывода процедуры
  • Console.Read и отправить входные данные в proc stdin
  • процесс завершается
person Joe    schedule 16.11.2011
comment
Я тоже не думаю, что это решение. У меня нет проблем с моим вводом, но я думаю, что немного запутал людей своим временным методом ввода. Удалите эти две строки: string inputText = Console.ReadLine(); proc.StandardInput.WriteLine(inputText); И измените вопрос: почему на экране не отображается «Нажмите любую клавишу, чтобы продолжить...». - person Owen; 16.11.2011

Я понял, что он не читал последнюю строку, потому что событие data received срабатывало только после того, как была выведена целая строка, но когда оно приостанавливается или задает вопрос, курсор все еще остается на той же строке. Новый поток, постоянно проверяющий поток вывода, стал решением этой небольшой проблемы.

Мне также удалось решить всю мою проблему, так что теперь то, что я получаю при запуске скрипта, очень похоже на окно командной строки.

Вот очень краткое описание того, как это работает:

Я запускаю новый процесс, который запускает пакетный файл. В то же время я запускаю новый поток, который постоянно зацикливается, запрашивая у процесса больше вывода и сохраняя этот вывод в очереди. Мой таймер .NET постоянно проверяет очередь на наличие текста и выводит его на мою панель ajax. Я использую текстовое поле и ajax для ввода текста в процесс ввода и в мою панель ajax. Мне также нужно было много javascript, и мне пришлось использовать переменные сеанса, чтобы все работало гладко.

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="Scripts.aspx.cs" Inherits="MITool.Scripts" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <script type="text/javascript" language="javascript">
        var script = '';

        function ShowScriptModal() {
            $('#overlay').css({ width: $(document).width(), height: $(document).height(), 'display': 'block' }).animate({ opacity: 0.85 }, 0, function () { $('#scriptModal').show(); });
        }

        function ScriptInputKeypress(e) {
            if (e.keyCode == 13) {
                ScriptInput();
            }
        }

        function ScriptInput() {
            var txtInput = document.getElementById("txtInput");
            var input = txtInput.value;
            var hiddenInput = document.getElementById("hiddenInput");

            if (input == '')
                return;

            hiddenInput.value = input;

            txtInput.value = '';
        }

        function CheckForNewOutput() {
            var outputUpdatePanel = document.getElementById("OutputUpdatePanel");
            var pageScript = outputUpdatePanel.innerHTML;
            if (script != pageScript) {
                script = pageScript;
                ScrollToBottom();
            }
            setTimeout("CheckForNewOutput()", 100);
        }

        function ScrollToBottom() {
            $('#OutputPanel').scrollTop($('#OutputUpdatePanel').height());
        }
    </script>
    <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true" EnablePartialRendering="true" LoadScriptsBeforeUI="true" />
    <div id="scriptModal">
        <div id="ScriptInputOutput">
            <asp:Panel ID="OutputPanel" runat="server" Width="700" Height="250" ScrollBars="Vertical"
                Style="margin: 10px auto; border: 1px solid #CCC; padding: 5px;" ClientIDMode="Static">
                <controls:ScriptOutput ID="ScriptOutputControl" runat="server" />
            </asp:Panel>
            <asp:Panel ID="InputPanel" runat="server" DefaultButton="btnScriptInput" >
                <asp:TextBox ID="txtInput" runat="server" ClientIDMode="Static" onkeypress="ScriptInputKeypress(event)" />
                <asp:HiddenField ID="hiddenInput" runat="server" ClientIDMode="Static" />
                <asp:Button ID="btnScriptInput" runat="server" Text="Script Input" ClientIDMode="Static" OnClick="btnScriptInput_Click" style="display:none" />           
                <asp:Button ID="btnExit" runat="server" CssClass="floatRight" Text="Exit" OnClick="btnExit_Click" />
            </asp:Panel>
        </div>
    </div>
    <asp:Literal ID="litScript" runat="server" />
    <ul id="breadcrumb">
        <li><a href="/dashboard.aspx">Main page</a> &gt;</li>
        <li class="current">Scripts</li>
    </ul>
    <div class="content">
        <h2>
            <asp:Label ID="lblHeader" runat="server" Text="Scripts" /></h2>
        <div class="clear">
        </div>
        <div class="table-content">
            <asp:Label ID="lblMessage" runat="server" CssClass="redMessage" />
            <asp:Repeater ID="rptScripts" runat="server" OnItemCommand="rptScripts_ItemCommand">
                <HeaderTemplate>
                    <table class="table" cellpadding="0" cellspacing="0">
                        <tr>
                            <th>
                                ID
                            </th>
                            <th>
                                Name
                            </th>
                            <th>
                                Location
                            </th>
                            <th>
                            </th>
                        </tr>
                </HeaderTemplate>
                <ItemTemplate>
                    <tr>
                        <td>
                            <%# Eval("ScriptId") %>
                        </td>
                        <td>
                            <%# Eval("Name") %>
                        </td>
                        <td>
                            <%# Eval("Path") %>
                        </td>
                        <td>
                            <asp:LinkButton ID="btnRunScript" runat="server" Text="Run" CommandName="Run" CommandArgument='<%# Eval("ScriptId") %>' />
                        </td>
                    </tr>
                </ItemTemplate>
                <FooterTemplate>
                    </table>
                </FooterTemplate>
            </asp:Repeater>
            <div>
            </div>
        </div>
    </div>
    <script type="text/javascript" language="javascript">
        CheckForNewOutput();
    </script>
</asp:Content>

// ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using MITool.Data.ScriptManager;
using System.Diagnostics;
using System.IO;
using System.Web.Security;
using System.Web.Services;
using System.Collections.Concurrent;
using System.Threading;

namespace MITool
{
    public partial class Scripts : System.Web.UI.Page
    {
        ConcurrentQueue<char> ScriptOutputQueue
        {
            get
            {
                return (ConcurrentQueue<char>)Session["ScriptOutputQueue"];
            }
            set
            {
                Session["ScriptOutputQueue"] = value;
            }
        }
        Process CurrentProcess
        {
            get
            {
                return (Process)Session["CurrentProcess"];
            }
            set
            {
                Session["CurrentProcess"] = value;
            }
        }
        bool ScriptRunning
        {
            get
            {
                if (CurrentProcess == null)
                    return false;
                if (!CurrentProcess.HasExited || !CurrentProcess.StandardOutput.EndOfStream)
                    return true;
                return false;
            }
        }
        bool OutputProcessing
        {
            get
            {
                if (ScriptOutputQueue != null && ScriptOutputQueue.Count != 0)
                    return true;
                return false;
            }
        }

        Thread OutputThread;

        void Reset()
        {
            ScriptOutputControl.SetTimerEnabled(false);
            ScriptOutputControl.ClearOutputText();
            if (CurrentProcess != null && !CurrentProcess.HasExited)
                CurrentProcess.Kill();
            if (OutputThread != null && OutputThread.IsAlive)
                OutputThread.Abort();
            ScriptOutputQueue = new ConcurrentQueue<char>();

            litScript.Text = string.Empty;
            txtInput.Text = string.Empty;
        }

        void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack) return;

            Reset();

            FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
            string role = id.Ticket.UserData;

            ScriptData data = new ScriptData();
            List<Script> scripts = data.GetScriptsByRole(role);
            rptScripts.DataSource = scripts;
            rptScripts.DataBind();
        }

        protected void rptScripts_ItemCommand(object source, RepeaterCommandEventArgs e)
        {
            switch (e.CommandName)
            {
                case "Run": StartScript(Int32.Parse(e.CommandArgument.ToString()));
                    break;
            }
        }

        void StartScript(int id)
        {
            if (ScriptRunning || OutputProcessing)
                return;

            Reset();

            ScriptData data = new ScriptData();
            History history = new History()
            {
                UserName = HttpContext.Current.User.Identity.Name,
                BatchFileId = id,
                DateRun = DateTime.Now
            };
            data.CreateHistory(history);            

            Script script = data.GetScript(id);

            ProcessStartInfo psi = new ProcessStartInfo()
            {
                FileName = script.Path,
                RedirectStandardOutput = true,
                RedirectStandardInput = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };

            CurrentProcess = new Process();
            CurrentProcess.StartInfo = psi;

            OutputThread = new Thread(Output);

            CurrentProcess.Start();
            OutputThread.Start();

            ScriptOutputControl.SetTimerEnabled(true);

            litScript.Text = "<script type=\"text/javascript\" language=\"javascript\">ShowScriptModal();</script>";

            SetFocus("txtInput");
        }

        void Output()
        {
            while (ScriptRunning)
            {
                var x = CurrentProcess.StandardOutput.Read();
                if (x != -1)
                    ScriptOutputQueue.Enqueue((char)x);
            }
        }

        public void btnScriptInput_Click(object sender, EventArgs e)
        {
            string input = hiddenInput.Value.ToString();

            ScriptOutputControl.Input(input);

            foreach (char x in input.ToArray())
            {
                if (CurrentProcess != null && !CurrentProcess.HasExited)
                {
                    CurrentProcess.StandardInput.Write(x);
                }
                Thread.Sleep(1);
            }
        }

        protected void btnExit_Click(object sender, EventArgs e)
        {
            Reset();
        }
    }
}

// ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======//

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ScriptOutput.ascx.cs"
    Inherits="MITool.Controls.ScriptOutput" %>
<asp:Label ID="lblStats" runat="server" Style="color: Red" />
<br />
<asp:UpdatePanel ID="OutputUpdatePanel" runat="server" ClientIDMode="Static">
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="UpdateTimer" EventName="Tick" />
        <asp:AsyncPostBackTrigger ControlID="btnScriptInput" EventName="Click" />
    </Triggers>
    <ContentTemplate>
        <asp:Literal ID="litOutput" runat="server" />
    </ContentTemplate>
</asp:UpdatePanel>
<asp:Timer ID="UpdateTimer" Interval="100" runat="server" OnTick="UpdateTimer_Tick" Enabled="false" />

// ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace MITool.Controls
{
    public partial class ScriptOutput : System.Web.UI.UserControl
    {
        string Output
        {
            get
            {
                if (Session["Output"] != null)
                    return Session["Output"].ToString();
                return string.Empty;
            }
            set
            {
                Session["Output"] = value;
            }
        }

        ConcurrentQueue<char> ScriptOutputQueue
        {
            get
            {
                return (ConcurrentQueue<char>)Session["ScriptOutputQueue"];
            }
            set
            {
                Session["ScriptOutputQueue"] = value;
            }
        }
        Process CurrentProcess
        {
            get
            {
                return (Process)Session["CurrentProcess"];
            }
            set
            {
                Session["CurrentProcess"] = value;
            }
        }
        bool ScriptRunning
        {
            get
            {
                if (CurrentProcess == null)
                    return false;
                if (!CurrentProcess.HasExited || CurrentProcess.StandardOutput.Peek() != -1)
                    return true;
                return false;
            }
        }
        bool OutputProcessing
        {
            get
            {
                if (ScriptOutputQueue != null && ScriptOutputQueue.Count != 0)
                    return true;
                return false;
            }
        }

        public void SetTimerEnabled(bool enabled)
        {
            UpdateTimer.Enabled = enabled;
        }
        public void ClearOutputText()
        {
            Output = string.Empty;
            litOutput.Text = Output;
        }

        protected void UpdateTimer_Tick(object sender, EventArgs e)
        {
            ProcessOutput();

            if (!ScriptRunning && !OutputProcessing)
            {
                UpdateTimer.Enabled = false;

                Output += "<br />// SCRIPT END //<br />";
                litOutput.Text = Output;
            }
        }

        public void Input(string s) 
        {
            Output += "<br />// " + s + "<br />";
        }

        void ProcessOutput()
        {
            string s = string.Empty;

            while (ScriptOutputQueue != null && ScriptOutputQueue.Any())
            {
                char x;
                if (ScriptOutputQueue.TryDequeue(out x))
                {
                    s += x;
                }
            }

            if (s != string.Empty)
            {
                s = Server.HtmlEncode(s);
                s = s.Replace("\r\n", "<br />");

                Output += s;
            }

            litOutput.Text = Output;
        }
    }
}
person Owen    schedule 28.11.2011